[Update 2014.10.08] The code is now on github.
If you value brevity in tests like I do then maybe you would agree that mockito’s doAnswer statements contain much boilerplate. That is certainly not mockito’s fault but rather a result of java’s inflexible syntax. When testing, i.e. GWT code one will frequently find the need to mock RPC service interfaces:
Order someOrder = …
doAnswer(new Answer(){ @Override public Object answer(InvocationOnMock invocation) throws Throwable { AsyncCallback callback = (AsyncCallback)(invocation.getArguments()[2]); callback.onSuccess(someOrder); return null; } }).when(orderService).getOrderForCustomer(eq(123), eq(456), any(AsyncCallback.class));
By using the BaseAnswer class discusses here you can achieve much shorter statements:
Order someOrder = ... doAnswer(new BaseAnswer(){ @Override public void getOrderForCustomer(int customerId, int orderId, AsyncCallback callback) { callback.onSuccess(someOrder); } }).when(orderService).getOrderForCustomer(eq(123), eq(456), any(AsyncCallback.class));
And this is the BaseAnswer class:
import java.lang.reflect.Method; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.springframework.util.ReflectionUtils; public abstract class BaseAnswer implements Answer{ private Class[] getClasses(Object[] arguments){ Class[] argClasses = new Class[arguments.length]; for (int i=0;i<arguments.length;i++){ if (arguments[i]!=null) argClasses[i] = arguments[i].getClass(); } return argClasses; } private double rateMatch(Class[] providedClasses, Class[] declaredClasses){ double score = -1; if (providedClasses.length!=declaredClasses.length) return -1; for (int i=0;i<providedClasses.length;i++){ if (providedClasses[i] == null) score+=0.5; else{ if (!declaredClasses[i].isAssignableFrom(providedClasses[i])) return -1; score+=1; } } score = score/(double)providedClasses.length; return score; } private Method findMethodWithArguments(Object[] arguments){ Class[] argClasses = getClasses(arguments); Method[] methods = getClass().getDeclaredMethods(); Method bestMethod = null; double bestMatch = -1; for (Method method:methods){ double match = rateMatch(argClasses, method.getParameterTypes()); if (match>bestMatch){ bestMatch = match; bestMethod = method; } } return bestMethod; } private String argTypesToString(Object args[]){ Class[] classes = getClasses(args); String s = ""; String prefix=","; for (Class c:classes){ s+=prefix+c.getName(); prefix=","; } return s; } @SuppressWarnings("unchecked") @Override public T answer(InvocationOnMock invocation) throws Throwable { Object[] arguments = invocation.getArguments(); Method method = findMethodWithArguments(arguments); if (method == null) throw new RuntimeException("This answer does not declare a method with these argument types: "+argTypesToString(arguments)); ReflectionUtils.makeAccessible(method); return (T)method.invoke(this, arguments); } }