To check the number of interactions with a mock where the parameter in the method call is of a certain type, one can do
mock.someMethod(new FirstClass());
mock.someMethod(new OtherClass());
verify(mock, times(1)).someMethod(isA(FirstClass.class));
This will pass thanks to the call to isA
since someMethod
was called twice but only once with argument FirstClass
However, this pattern seems to not be possible when using an ArgumentCaptor, even if the Captor was created for the particular argument FirstClass
this doesn't work
mock.someMethod(new FirstClass());
mock.someMethod(new OtherClass());
ArgumentCaptor<FirstClass> captor = ArgumentCaptor.forClass(FirstClass.class);
verify(mock, times(1)).someMethod(captor.capture());
it says the mock was called more than once.
Is there any way to accomplish this verification while capturing the argument for further checking?
I recommend using Mockito's Hamcrest integration to write a good, clean matcher for it. That allows you to combine the verification with detailed checking of the passed argument:
verify(mock, times(1)).someMethod(argThat(personNamed("Bob")));
Matcher<Person> personNamed(final String name) {
return new TypeSafeMatcher<Person>() {
public boolean matchesSafely(Person item) {
return name.equals(item.getName());
}
public void describeTo(Description description) {
description.appendText("a Person named " + name);
}
};
}
Matchers generally lead to more readable tests and more useful test failure messages. They also tend to be very reusable, and you'll find yourself building up a library of them tailored for testing your project. Finally, you can also use them for normal test assertions using JUnit's Assert.assertThat()
, so you get double use out of them.
Quoting the docs:
Note that an
ArgumentCaptor
don't do any type checks , it is only there to avoid casting in your code. This might however change (type checks could be added) in a future major release.
I wouldn't use an ArgumentCaptor
for this. This class captures (literally) everything, despite what class was provided as it's .forClass
argument.
To achieve what you want I suggest intercept the argument using Mockito's Answer
interface:
private FirstClass lastArgument;
@Test
public void captureFirstClass() throws Exception {
doAnswer(captureLastArgument()).when(mock).someMethod(anInstanceOfFirstClass());
mock.someMethod(new FirstClass());
mock.someMethod(new OtherClass());
verify(mock, times(1)).someMethod(anInstanceOfFirstClass());
//write your desired matchers against lastArgument object
}
private Answer<FirstClass> captureLastArgument() {
return new Answer<FirstClass>() {
@Override
public FirstClass answer(InvocationOnMock invocation) throws Throwable {
TestClass.this.lastArgument = (FirstClass) invocation.getArguments()[0];
return null;
}
};
}
private static Object anInstanceOfFirstClass(){
return Mockito.argThat(isA(FirstClass.class));
}
You can use the the captor for the sake of capturing, then verify the number of invocations with each argument type separately.
// given
ArgumentCaptor<AA> captor = ArgumentCaptor.forClass(AA.class);
CC cc = new CC();
// when
cut.someMethod(new AA());
cut.someMethod(new BB());
cut.someMethod(new BB());
cut.someMethod(cc);
// then
Mockito.verify(collaborator, atLeastOnce()).someMethod(captor.capture());
Mockito.verify(collaborator, times(1)).someMethod(isA(AA.class));
Mockito.verify(collaborator, times(2)).someMethod(isA(BB.class));
Mockito.verify(collaborator, times(1)).someMethod(isA(CC.class));
assertEquals(cc, captor.getValue());
Apparently the generic type of the captor reference doesn't affect anything at runtime.
I also encountered this problem today. I thought I could simply do something like
verify(mock).someMethod(and(isA(FirstClass.class), captor.capture()));
but I couldn't get it to work. I ended up with this solution:
@Test
public void Test() throws Exception {
final ArgumentCaptor<FirstClass> captor = ArgumentCaptor.forClass(FirstClass.class);
mock.someMethod(new FirstClass());
mock.someMethod(new OtherClass());
verify(eventBus, atLeastOnce()).post(captor.capture());
final List<FirstClass> capturedValues = typeCheckedValues(captor.getAllValues(), FirstClass.class);
assertThat(capturedValues.size(), is(1));
final FirstClass capturedValue = capturedValues.get(0);
// Do assertions on capturedValue
}
private static <T> List<T> typeCheckedValues(List<T> values, Class<T> clazz) {
final List<T> typeCheckedValues = new ArrayList<>();
for (final T value : values) {
if (clazz.isInstance(value)) {
typeCheckedValues.add(value);
}
}
return typeCheckedValues;
}
Note: if only one class needs to be captured in this way typeCheckedValues
can be simplified into:
private static List<FirstClass> typeCheckedValues(List<FirstClass> values) {
final List<FirstClass> typeCheckedValues = new ArrayList<>();
for (final Object value : values) {
if (value instanceof FirstClass) {
typeCheckedValues.add((FirstClass) value);
}
}
return typeCheckedValues;
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.