Is there a way to capture a list of specific type using mockitos ArgumentCaptore. This doesn't work:
ArgumentCaptor<ArrayList<SomeType>> argument = ArgumentCaptor.forClass(ArrayList.class);
Is there a way to capture a list of specific type using mockitos ArgumentCaptore. This doesn't work:
ArgumentCaptor<ArrayList<SomeType>> argument = ArgumentCaptor.forClass(ArrayList.class);
The nested generics-problem can be avoided with the @Captor annotation:
public class Test{
@Mock
private Service service;
@Captor
private ArgumentCaptor<ArrayList<SomeType>> captor;
@Before
public void init(){
MockitoAnnotations.initMocks(this);
}
@Test
public void shouldDoStuffWithListValues() {
//...
verify(service).doStuff(captor.capture()));
}
}
MockitoAnnotations.initMocks(this)
in the @Before
method rather than using a runner that excludes the ability to use another runner. However, +1, thanks for pointing out the annotation. –
Meredeth initMocks(this)
should be replaced with openMocks(this)
, as the first one ist deprecated –
Piddle @org.junit.jupiter.api.extension.ExtendWith(org.mockito.junit.jupiter.MockitoExtension.class)
annotation at class level instead of MockitoAnnotations.initMocks(this);
. –
Goodness Yeah, this is a general generics problem, not mockito-specific.
There is no class object for ArrayList<SomeType>
, and thus you can't type-safely pass such an object to a method requiring a Class<ArrayList<SomeType>>
.
You can cast the object to the right type:
Class<ArrayList<SomeType>> listClass =
(Class<ArrayList<SomeType>>)(Class)ArrayList.class;
ArgumentCaptor<ArrayList<SomeType>> argument = ArgumentCaptor.forClass(listClass);
This will give some warnings about unsafe casts, and of course your ArgumentCaptor can't really differentiate between ArrayList<SomeType>
and ArrayList<AnotherType>
without maybe inspecting the elements.
(As mentioned in the other answer, while this is a general generics problem, there is a Mockito-specific solution for the type-safety problem with the @Captor
annotation. It still can't distinguish between an ArrayList<SomeType>
and an ArrayList<OtherType>
.)
Take also a look at tenshi's comment. You can change the original code to this simplified version:
final ArgumentCaptor<List<SomeType>> listCaptor
= ArgumentCaptor.forClass((Class) List.class);
ArgumentCaptor<List<SimeType>> argument = ArgumentCaptor.forClass((Class) List.class);
–
Triiodomethane @SuppressWarnings("unchecked")
annotation above the argument captor definition line. Also, casting to Class
is redundant. –
Sesquipedalian Class
is not redundant in my tests. –
Mating If you're not afraid of old java-style (non type safe generic) semantics, this also works and is simple'ish:
ArgumentCaptor<List> argument = ArgumentCaptor.forClass(List.class);
verify(subject).method(argument.capture()); // run your code
List<SomeType> list = argument.getValue(); // first captured List, etc.
List<String> mockedList = mock(List.class);
List<String> l = new ArrayList();
l.add("someElement");
mockedList.addAll(l);
ArgumentCaptor<List> argumentCaptor = ArgumentCaptor.forClass(List.class);
verify(mockedList).addAll(argumentCaptor.capture());
List<String> capturedArgument = argumentCaptor.<List<String>>getValue();
assertThat(capturedArgument, hasItem("someElement"));
Based on @tenshi's and @pkalinow's comments (also kudos to @rogerdpack), the following is a simple solution for creating a list argument captor that also disables the "uses unchecked or unsafe operations" warning:
@SuppressWarnings("unchecked")
final ArgumentCaptor<List<SomeType>> someTypeListArgumentCaptor =
ArgumentCaptor.forClass(List.class);
Full example here and corresponding passing CI build and test run here.
Our team has been using this for some time in our unit tests and this looks like the most straightforward solution for us.
For an earlier version of junit, you can do
Class<Map<String, String>> mapClass = (Class) Map.class;
ArgumentCaptor<Map<String, String>> mapCaptor = ArgumentCaptor.forClass(mapClass);
I had the same issue with testing activity in my Android app. I used ActivityInstrumentationTestCase2
and MockitoAnnotations.initMocks(this);
didn't work.
I solved this issue with another class with respectively field. For example:
class CaptorHolder {
@Captor
ArgumentCaptor<Callback<AuthResponse>> captor;
public CaptorHolder() {
MockitoAnnotations.initMocks(this);
}
}
Then, in activity test method:
HubstaffService hubstaffService = mock(HubstaffService.class);
fragment.setHubstaffService(hubstaffService);
CaptorHolder captorHolder = new CaptorHolder();
ArgumentCaptor<Callback<AuthResponse>> captor = captorHolder.captor;
onView(withId(R.id.signInBtn))
.perform(click());
verify(hubstaffService).authorize(anyString(), anyString(), captor.capture());
Callback<AuthResponse> callback = captor.getValue();
There is an open issue in Mockito's GitHub about this exact problem.
I have found a simple workaround that does not force you to use annotations in your tests:
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.MockitoAnnotations;
public final class MockitoCaptorExtensions {
public static <T> ArgumentCaptor<T> captorFor(final CaptorTypeReference<T> argumentTypeReference) {
return new CaptorContainer<T>().captor;
}
public static <T> ArgumentCaptor<T> captorFor(final Class<T> argumentClass) {
return ArgumentCaptor.forClass(argumentClass);
}
public interface CaptorTypeReference<T> {
static <T> CaptorTypeReference<T> genericType() {
return new CaptorTypeReference<T>() {
};
}
default T nullOfGenericType() {
return null;
}
}
private static final class CaptorContainer<T> {
@Captor
private ArgumentCaptor<T> captor;
private CaptorContainer() {
MockitoAnnotations.initMocks(this);
}
}
}
What happens here is that we create a new class with the @Captor
annotation and inject the captor into it. Then we just extract the captor and return it from our static method.
In your test you can use it like so:
ArgumentCaptor<Supplier<Set<List<Object>>>> fancyCaptor = captorFor(genericType());
Or with syntax that resembles Jackson's TypeReference
:
ArgumentCaptor<Supplier<Set<List<Object>>>> fancyCaptor = captorFor(
new CaptorTypeReference<Supplier<Set<List<Object>>>>() {
}
);
It works, because Mockito doesn't actually need any type information (unlike serializers, for example).
Since Mockito 5.3.0 you can circumvent that problem with the assertArg
matcher.1
Assuming you want to do assertions on a method with signature
interface MyInterface {
MyResultType calculateSomething(List<MyType> aList)
}
you can just call (using AssertJ here)
verify(myInterfaceMock).calculateSomething(
assertArg(theList -> assertThat(theList)
//inside here theList has the correct type
.isNotNull()
.isNotEmpty()
)
);
For readability i prefer to put the assertions into a variable.
final Consumer<? extends List<MyType>> myListAssertions = theList ->
assertThat(theList)
.isNotNull()
.isNotEmpty();
verify(myInterfaceMock).calculateSomething( assertArg(myListAssertions) );
This works well, with failed assertions pointing to the correct line where the actual assertion is defined.
© 2022 - 2024 — McMap. All rights reserved.
ArrayList
). You can always useList
interface, and if you want represent the fact, that it's covariant, then you can useextends
:ArgumentCaptor<? extends List<SomeType>>
– Triiodomethane