Compact mockito: shorter answer notation

[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);
}
}

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s