Existe uma maneira de capturar uma lista de tipos específicos usando mockitos ArgumentCaptore. Isso não funciona:
ArgumentCaptor<ArrayList<SomeType>> argument = ArgumentCaptor.forClass(ArrayList.class);
Existe uma maneira de capturar uma lista de tipos específicos usando mockitos ArgumentCaptore. Isso não funciona:
ArgumentCaptor<ArrayList<SomeType>> argument = ArgumentCaptor.forClass(ArrayList.class);
Respostas:
O problema genérico aninhado pode ser evitado com a anotação @Captor :
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)
o @Before
método do que usar um corredor que exclua a capacidade de usar outro corredor. No entanto, +1, obrigado por apontar a anotação.
Sim, esse é um problema genérico geral, não específico de mockito.
Não existe um objeto de classe ArrayList<SomeType>
e, portanto, você não pode digitar com segurança esse objeto para um método que exija a Class<ArrayList<SomeType>>
.
Você pode converter o objeto no tipo certo:
Class<ArrayList<SomeType>> listClass =
(Class<ArrayList<SomeType>>)(Class)ArrayList.class;
ArgumentCaptor<ArrayList<SomeType>> argument = ArgumentCaptor.forClass(listClass);
Isso fornecerá alguns avisos sobre lançamentos inseguros e, é claro, seu ArgumentCaptor não pode realmente diferenciar entre ArrayList<SomeType>
e ArrayList<AnotherType>
sem talvez inspecionar os elementos.
(Conforme mencionado na outra resposta, embora esse seja um problema genérico geral, existe uma solução específica do Mockito para o problema de segurança de tipo na @Captor
anotação. Ainda não é possível distinguir entre um ArrayList<SomeType>
e um ArrayList<OtherType>
.)
Veja também o comentário de tenshi . Você pode alterar o código original de Paŭlo Ebermann para este (muito mais simples)
final ArgumentCaptor<List<SomeType>> listCaptor
= ArgumentCaptor.forClass((Class) List.class);
ArgumentCaptor<List<SimeType>> argument = ArgumentCaptor.forClass((Class) List.class);
@SuppressWarnings("unchecked")
anotação acima da linha de definição do captador de argumento. Além disso, transmitir para Class
é redundante.
Class
não é redundante nos meus testes.
Se você não tem medo da semântica antiga do estilo java (não genérico seguro), isso também funciona e é razoavelmente simples.
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"));
Com base nos comentários de @ tenshi e @ pkalinow (também parabéns a @rogerdpack), a seguir é uma solução simples para criar um captador de argumentos de lista que também desativa o aviso "usa operações não verificadas ou não seguras" :
@SuppressWarnings("unchecked")
final ArgumentCaptor<List<SomeType>> someTypeListArgumentCaptor =
ArgumentCaptor.forClass(List.class);
Exemplo completo aqui e construção e teste de ICs correspondentes correspondentes executados aqui .
Nossa equipe vem usando isso há algum tempo em nossos testes de unidade e essa parece a solução mais direta para nós.
Para uma versão anterior do junit, você pode fazer
Class<Map<String, String>> mapClass = (Class) Map.class;
ArgumentCaptor<Map<String, String>> mapCaptor = ArgumentCaptor.forClass(mapClass);
Eu tive o mesmo problema com a atividade de teste no meu aplicativo Android. Eu usei ActivityInstrumentationTestCase2
e MockitoAnnotations.initMocks(this);
não funcionei. Eu resolvi esse problema com outra classe com o campo respectivamente. Por exemplo:
class CaptorHolder {
@Captor
ArgumentCaptor<Callback<AuthResponse>> captor;
public CaptorHolder() {
MockitoAnnotations.initMocks(this);
}
}
Em seguida, no método de teste de atividade:
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();
Há um problema em aberto no GitHub do Mockito sobre esse problema exato.
Eu encontrei uma solução simples que não o força a usar anotações em seus testes:
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);
}
}
}
O que acontece aqui é que criamos uma nova classe com a @Captor
anotação e injetamos o captador nela. Depois, extraímos o captador e o devolvemos do nosso método estático.
No seu teste, você pode usá-lo da seguinte maneira:
ArgumentCaptor<Supplier<Set<List<Object>>>> fancyCaptor = captorFor(genericType());
Ou com uma sintaxe semelhante à de Jackson TypeReference
:
ArgumentCaptor<Supplier<Set<List<Object>>>> fancyCaptor = captorFor(
new CaptorTypeReference<Supplier<Set<List<Object>>>>() {
}
);
Funciona porque o Mockito não precisa de nenhuma informação de tipo (ao contrário dos serializadores, por exemplo).
ArrayList
). Você sempre pode usarList
interface, e se você quer representar o fato, de que é co-variante, então você pode usarextends
:ArgumentCaptor<? extends List<SomeType>>