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:
@RunWith(MockitoJUnitRunner.class)
public class Test{
@Mock
private Service service;
@Captor
private ArgumentCaptor<ArrayList<SomeType>> captor;
@Test
public void shouldDoStuffWithListValues() {
//...
verify(service).doStuff(captor.capture()));
}
}
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 tenshis comment. You can change the original code from Paŭlo Ebermann to this (much simpler)
final ArgumentCaptor<List<SomeType>> listCaptor
= ArgumentCaptor.forClass((Class) List.class);
If you're not afraid of old java-style (non type safe generic) semantics, this also works and is reasonably 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.
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();
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);