001 /* 002 * Copyright 2013 Google Inc. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 005 * use this file except in compliance with the License. You may obtain a copy of 006 * the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 012 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 013 * License for the specific language governing permissions and limitations under 014 * the License. 015 */ 016 package com.google.gwtmockito; 017 018 import static org.mockito.Mockito.mock; 019 020 import com.google.gwt.core.client.GWT; 021 import com.google.gwt.core.client.GWTBridge; 022 import com.google.gwt.i18n.client.Messages; 023 import com.google.gwt.resources.client.ClientBundle; 024 import com.google.gwt.resources.client.CssResource; 025 import com.google.gwt.safehtml.client.SafeHtmlTemplates; 026 import com.google.gwt.uibinder.client.UiBinder; 027 import com.google.gwt.user.client.rpc.RemoteService; 028 import com.google.gwtmockito.fakes.FakeClientBundleProvider; 029 import com.google.gwtmockito.fakes.FakeMessagesProvider; 030 import com.google.gwtmockito.fakes.FakeProvider; 031 import com.google.gwtmockito.fakes.FakeUiBinderProvider; 032 import com.google.gwtmockito.impl.ReturnsCustomMocks; 033 034 import org.mockito.Mockito; 035 import org.mockito.MockitoAnnotations; 036 037 import java.lang.reflect.Field; 038 import java.lang.reflect.InvocationTargetException; 039 import java.lang.reflect.Method; 040 import java.util.HashMap; 041 import java.util.Map; 042 import java.util.Map.Entry; 043 044 /** 045 * A library to make Mockito-based testing of GWT applications easier. Most 046 * users won't have to reference this class directly and should instead use 047 * {@link GwtMockitoTestRunner}. Users who cannot use that class (e.g. tests 048 * using JUnit3) can invoke {@link #initMocks} directly in their setUp and 049 * {@link #tearDown} in their tearDown methods. 050 * <p> 051 * Note that calling {@link #initMocks} and {@link #tearDown} directly does 052 * <i>not</i> implement {@link GwtMockitoTestRunner}'s behavior of implementing 053 * native methods and making final methods mockable. The only way to get this 054 * behavior is by using {@link GwtMockitoTestRunner}. 055 * <p> 056 * Once {@link #initMocks} has been invoked, test code can safely call 057 * GWT.create without exceptions. Doing so will return either a mock object 058 * registered with {@link GwtMock}, a fake object specified by a call to 059 * {@link #useProviderForType}, or a new mock instance if no other binding 060 * exists. Fakes for types extending the following are provided by default: 061 * <ul> 062 * <li> UiBinder: uses a fake that populates all UiFields with GWT.create'd 063 * widgets, allowing them to be mocked like other calls to GWT.create. 064 * See {@link FakeUiBinderProvider} for details. 065 * <li> ClientBundle: Uses a fake that will return fake CssResources as 066 * defined below, and will return fake versions of other resources that 067 * return unique strings for getText and getSafeUri. See 068 * {@link FakeClientBundleProvider} for details. 069 * <li> Messages, CssResource, and SafeHtmlTemplates: uses a fake that 070 * implements each method by returning a String of SafeHtml based on the 071 * name of the method and any arguments passed to it. The exact format is 072 * undefined. See {@link FakeMessagesProvider} for details. 073 * </ul> 074 * <p> 075 * The type returned from GWT.create will generally be the same as the type 076 * passed in. The exception is when GWT.create'ing a subclass of 077 * {@link RemoteService} - in this case, the result of GWT.create will be the 078 * Async version of that interface as defined by gwt-rpc. 079 * <p> 080 * If {@link #initMocks} is called manually, it is important to invoke 081 * {@link #tearDown} once the test has been completed. Failure to do so can 082 * cause state to leak between tests. 083 * 084 * @see GwtMockitoTestRunner 085 * @see GwtMock 086 * @author ekuefler@google.com (Erik Kuefler) 087 */ 088 public class GwtMockito { 089 090 private static Bridge bridge; 091 092 /** 093 * Causes all calls to GWT.create to be intercepted to return a mock or fake 094 * object, and populates any {@link GwtMock}-annotated fields with mockito 095 * mocks. This method should be usually be called during the setUp method of a 096 * test case. Note that it explicitly calls 097 * {@link MockitoAnnotations#initMocks}, so there is no need to call that 098 * method separately. See the class description for more details. 099 * 100 * @param owner class to scan for {@link GwtMock}-annotated fields - almost 101 * always "this" in unit tests 102 */ 103 public static void initMocks(Object owner) { 104 // Create a new bridge and register built-in type providers 105 bridge = new Bridge(); 106 useProviderForType(ClientBundle.class, new FakeClientBundleProvider()); 107 useProviderForType(CssResource.class, new FakeMessagesProvider<CssResource>()); 108 useProviderForType(Messages.class, new FakeMessagesProvider<Messages>()); 109 useProviderForType(SafeHtmlTemplates.class, new FakeMessagesProvider<SafeHtmlTemplates>()); 110 useProviderForType(UiBinder.class, new FakeUiBinderProvider()); 111 112 // Install the bridge and populate mock fields 113 boolean success = false; 114 try { 115 setGwtBridge(bridge); 116 registerGwtMocks(owner); 117 MockitoAnnotations.initMocks(owner); 118 success = true; 119 } finally { 120 if (!success) { 121 tearDown(); 122 } 123 } 124 } 125 126 /** 127 * Resets GWT.create to its default behavior. This method should be called 128 * after any test that called initMocks completes, usually in your test's 129 * tearDown method. Failure to do so can introduce unexpected ordering 130 * dependencies in tests. 131 */ 132 public static void tearDown() { 133 setGwtBridge(null); 134 } 135 136 /** 137 * Specifies that the given provider should be used to GWT.create instances of 138 * the given type and its subclasses. Note that if you just want to return a 139 * Mockito mock from GWT.create, it's probably easier to use {@link GwtMock} 140 * instead. 141 */ 142 public static void useProviderForType(Class<?> type, FakeProvider<?> provider) { 143 if (bridge == null) { 144 throw new IllegalStateException("Must call initMocks() before calling useProviderForType()"); 145 } 146 if (bridge.registeredMocks.containsKey(type)) { 147 throw new IllegalArgumentException( 148 "Can't use a provider for a type that already has a @GwtMock declared"); 149 } 150 bridge.registeredProviders.put(type, provider); 151 } 152 153 private static void registerGwtMocks(Object owner) { 154 Class<? extends Object> clazz = owner.getClass(); 155 156 while (!"java.lang.Object".equals(clazz.getName())) { 157 for (Field field : clazz.getDeclaredFields()) { 158 if (field.isAnnotationPresent(GwtMock.class)) { 159 Object mock = Mockito.mock(field.getType()); 160 if (bridge.registeredMocks.containsKey(field.getType())) { 161 throw new IllegalArgumentException("Owner declares multiple @GwtMocks for type " 162 + field.getType().getSimpleName() + "; only one is allowed"); 163 } 164 bridge.registeredMocks.put(field.getType(), mock); 165 field.setAccessible(true); 166 try { 167 field.set(owner, mock); 168 } catch (IllegalAccessException e) { 169 throw new IllegalStateException("Failed to make field accessible: " + field); 170 } 171 } 172 } 173 174 clazz = clazz.getSuperclass(); 175 } 176 } 177 178 private static void setGwtBridge(GWTBridge bridge) { 179 try { 180 Method setBridge = GWT.class.getDeclaredMethod("setBridge", GWTBridge.class); 181 setBridge.setAccessible(true); 182 setBridge.invoke(null, bridge); 183 } catch (SecurityException e) { 184 throw new RuntimeException(e); 185 } catch (InvocationTargetException e) { 186 throw new RuntimeException(e.getCause()); 187 } catch (IllegalAccessException e) { 188 throw new AssertionError("Impossible since setBridge was made accessible"); 189 } catch (NoSuchMethodException e) { 190 throw new AssertionError("Impossible since setBridge is known to exist"); 191 } 192 } 193 194 private static class Bridge extends GWTBridge { 195 private final Map<Class<?>, FakeProvider<?>> registeredProviders = 196 new HashMap<Class<?>, FakeProvider<?>>(); 197 private final Map<Class<?>, Object> registeredMocks = new HashMap<Class<?>, Object>(); 198 199 @Override 200 @SuppressWarnings("unchecked") // safe since we check whether the type is assignable 201 public <T> T create(Class<?> type) { 202 // Handle RemoteServices specially - GWT.create'ing them should return the Async version 203 if (RemoteService.class.isAssignableFrom(type)) { 204 Class<?> asyncType; 205 try { 206 asyncType = Class.forName(type.getCanonicalName() + "Async"); 207 } catch (ClassNotFoundException e) { 208 throw new IllegalArgumentException( 209 type.getCanonicalName() + " does not have a corresponding async interface", e); 210 } 211 if (registeredMocks.containsKey(asyncType)) { 212 return (T) registeredMocks.get(asyncType); 213 } else { 214 return (T) mock(asyncType); 215 } 216 } 217 218 // Otherwise, first check if we have a GwtMock for this exact type and use it if so. 219 if (registeredMocks.containsKey(type)) { 220 return (T) registeredMocks.get(type); 221 } 222 223 // Next see if we have a provider for this type or a supertype. 224 for (Entry<Class<?>, FakeProvider<?>> entry : registeredProviders.entrySet()) { 225 if (entry.getKey().isAssignableFrom(type)) { 226 // We know this is safe since we just checked that the type can be assigned to the entry 227 @SuppressWarnings({"rawtypes", "cast"}) 228 Class rawType = (Class) type; 229 return (T) entry.getValue().getFake(rawType); 230 } 231 } 232 233 // If nothing has been registered, just return a new mock object to avoid NPEs. 234 return (T) mock(type, new ReturnsCustomMocks()); 235 } 236 237 @Override 238 public String getVersion() { 239 return getClass().getName(); 240 } 241 242 @Override 243 public boolean isClient() { 244 return false; 245 } 246 247 @Override 248 public void log(String message, Throwable e) { 249 System.err.println(message + "\n"); 250 if (e != null) { 251 e.printStackTrace(); 252 } 253 } 254 } 255 }