Reputation: 298818
I have a largish codebase with many decorator classes that usually delegate all but one method to a delegate object, i.e. something like this:
class WrapperThing implements Thing{
private final Thing delegate;
WrapperThing(Thing thing){this.delegate=thing;}
public boolean method1(){ return delegate.method1(); }
public String method2(int arg1, boolean arg2){ return delegate.method2(arg1, arg2); }
// lots more methods here, all delegating to delegate
}
Now I am creating Unit Tests for these wrappers, using a Junit 5 @TestFactory
, calling each of the methods on the WrapperThing
, and want to verify that there was an invocation on the wrapped delegate, which is a Mockito mock.
Here's my code so far:
private void testMethodDelegation(final Method method) {
D delegate = mock(delegateType);
W wrapper = createWrapper(delegate);
List<Object> args = new ArrayList<>(method.getParameterTypes().length + 1);
args.add(wrapper);
gatherMethodArgs(method, args); // populate args with mocks or default values
try {
method.invoke(args.toArray(new Object[0]));
}catch(Exception e) {
// this is fine, we're just testing the delegation
}
// now comes the verify part
List<Object> mockArgs = new ArrayList<>();
try {
mockArgs.add(verify(delegate));
mockArgs.addAll(nCopies(args.size()-1, any()));
method.invoke(mockArgs.toArray(new Object[0]));
}catch (Exception e) {
throw new IllegalStateException(e);
}
}
When I run this, the error I get is:
org.mockito.exceptions.misusing.InvalidUseOfMatchersException:
Misplaced or misused argument matcher detected here:
-> at some.packagename.AbstractDelegateTest.testMethodDelegation(AbstractDelegateTest.java:81)
You cannot use argument matchers outside of verification or stubbing.
Examples of correct usage of argument matchers:
when(mock.get(anyInt())).thenReturn(null);
doThrow(new RuntimeException()).when(mock).someVoidMethod(anyObject());
verify(mock).someMethod(contains("foo"))
Am I doing something wrong, or is it not possible to verify a method call if you don't know the exact method?
Upvotes: 1
Views: 217
Reputation: 298818
The problem is that I was calling method.invoke() wrong.
I thought the format was method.invoke([target, arg1, ... argn])
, but it is actually method.invoke(target, [arg1, ... argn])
. It's been a long day, my bad.
This code works:
private void testMethodDelegation(final Method method) {
D delegate = mock(delegateType);
W wrapper = createWrapper(delegate);
List<Object> args = new ArrayList<>(method.getParameterTypes().length);
gatherMethodArgs(method, args); // populate args with mocks or default values
try {
method.invoke(wrapper, args.toArray(new Object[0]));
} catch (Exception e) {
// this is fine, we're just testing the delegation
throw new IllegalStateException(e);
}
callVerify(method, delegate);
}
private void callVerify(final Method method, final D delegate) {
// now comes the verify part
List<Object> mockArgs = new ArrayList<>(method.getParameterTypes().length);
try {
D verifyDelegate = verify(delegate);
gatherVerifyArgs(method, mockArgs);
method.invoke(verifyDelegate, mockArgs.toArray(new Object[0]));
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
private void gatherVerifyArgs(final Method method, final List<Object> args) {
for (Class<?> parameterType : method.getParameterTypes()) {
if (int.class == parameterType) {
args.add(anyInt());
} else if (boolean.class == parameterType) {
args.add(anyBoolean());
} else if (long.class == parameterType) {
args.add(anyLong());
} else if (double.class == parameterType) {
args.add(anyDouble());
} else if (float.class == parameterType) {
args.add(anyFloat());
} else if (String.class == parameterType) {
args.add(anyString());
} else {
args.add(any());
}
}
}
private void gatherMethodArgs(final Method method, final List<Object> args) {
int i = 0;
for (Class<?> type : method.getParameterTypes()) {
try {
if (type == String.class) {
args.add("");
} else if (type.isArray()) {
args.add(Array.newInstance(type.getComponentType(), 0));
} else if (Primitives.allPrimitiveTypes().contains(type)) {
args.add(Defaults.defaultValue(type));
} else if (Primitives.allWrapperTypes().contains(type)) {
args.add(Defaults.defaultValue(Primitives.unwrap(type)));
} else if (type == List.class) {
args.add(ImmutableList.of());
} else if (type == Set.class) {
args.add(ImmutableSet.of());
} else if (type == Map.class) {
args.add(ImmutableMap.of());
} else if (type.getName().startsWith("java.util.")) {
args.add(type.newInstance());
} else {
args.add(mock(type));
}
} catch (Exception e) {
throw new IllegalStateException(
String.format("Error mocking parameter %d (%s) of method %s", i,
method.getGenericParameterTypes()[i], method), e);
}
i++;
}
}
Upvotes: 1