Mockito: Tentar espionar o método está chamando o método original

350

Estou usando o Mockito 1.9.0. Eu quero simular o comportamento de um único método de uma classe em um teste JUnit, então eu tenho

final MyClass myClassSpy = Mockito.spy(myInstance);
Mockito.when(myClassSpy.method1()).thenReturn(myResults);

O problema é que, na segunda linha, myClassSpy.method1()está realmente sendo chamado, resultando em uma exceção. A única razão pela qual estou usando zombarias é que, mais tarde, sempre que myClassSpy.method1()for chamado, o método real não será chamado e o myResultsobjeto será retornado.

MyClassé uma interface e myInstanceé uma implementação disso, se isso importa.

O que preciso fazer para corrigir esse comportamento de espionagem?

Dave
fonte
Dê uma olhada no seguinte: stackoverflow.com/a/29394497/355438
Lu55

Respostas:

609

Deixe-me citar a documentação oficial :

Pegadinha importante na espionagem de objetos reais!

Às vezes é impossível usar when (Object) para espionagem de espiões. Exemplo:

List list = new LinkedList();
List spy = spy(list);

// Impossible: real method is called so spy.get(0) throws IndexOutOfBoundsException (the list is yet empty)
when(spy.get(0)).thenReturn("foo");

// You have to use doReturn() for stubbing
doReturn("foo").when(spy).get(0);

No seu caso, é algo como:

doReturn(resulstIWant).when(myClassSpy).method1();
Tomasz Nurkiewicz
fonte
27
E se eu usar esse método e o meu original ainda estiver sendo chamado? Pode haver algum problema com os parâmetros que eu passo? Aqui está todo o teste: pastebin.com/ZieY790P send método está sendo chamado
Evgeni Petrov
26
@ EvvgeniPetrov, se o método original ainda estiver sendo chamado, provavelmente porque o método original é final. Mockito não zomba dos métodos finais e não pode alertá-lo sobre a zombaria dos métodos finais.
MarcG
11
isso também é possível para doThrow ()?
precisa saber é o seguinte
11
Sim, infelizmente, os métodos estáticos são imbatíveis e "não podem ser espionados". O que faço para lidar com métodos estáticos é envolver um método em torno da chamada estática e usar um doNothing ou doReturn nesse método. Com singletons ou objetos de escala, movo a essência da lógica para uma classe abstrata e isso me dá a capacidade de ter uma classe de teste alternativa impl do objeto em que posso criar um espião.
Andrew Norman
24
E se o método NOT final e NOT static ainda estiver sendo chamado?
X-HuMan 27/10
27

Meu caso foi diferente da resposta aceita. Eu estava tentando zombar de um método privado de pacote para uma instância que não vivia nesse pacote

package common;

public class Animal {
  void packageProtected();
}

package instances;

class Dog extends Animal { }

e as classes de teste

package common;

public abstract class AnimalTest<T extends Animal> {
  @Before
  setup(){
    doNothing().when(getInstance()).packageProtected();
  }

  abstract T getInstance();
}

package instances;

class DogTest extends AnimalTest<Dog> {
  Dog getInstance(){
    return spy(new Dog());
  }

  @Test
  public void myTest(){}
}

A compilação está correta, mas quando tenta configurar o teste, invoca o método real.

Declarar o método protegido ou público corrige o problema, embora não seja uma solução limpa.

Maragues
fonte
2
Corri para um problema semelhante, mas o teste e o método private-package estavam no mesmo pacote. Eu acho que talvez o Mockito tenha problemas com métodos de pacote privado em geral.
Dave
22

No meu caso, usando o Mockito 2.0, tive que alterar todos os any()parâmetros nullable()para stub da chamada real.

ejaenv
fonte
2
Não deixe que a resposta top 321 votada o derrube, isso resolveu meu problema :) Estou lutando com isso há algumas horas!
Chris Kessel
3
Essa foi a resposta para mim. Para tornar ainda mais fácil para aqueles que seguem quando zombando de seu método a sintaxe é: foo = Mockito.spy(foo); Mockito.doReturn(someValue).when(foo).methodToPrevent(nullable(ArgumentType.class));
Stryder
Com Mockito 2.23.4 posso confirmar isto não é necessário, ele funciona bem com anye eqmatchers.
26919 vmaldosan
2
Tentei três abordagens diferentes na versão 2.23.4 lib: any (), eq () e anulável (). Apenas o mais tarde trabalhou
ryzhman 08/04/19
Olá, sua solução foi muito boa e funcionou para mim também. Obrigado
Dhiren Solanki
16

A resposta de Tomasz Nurkiewicz parece não contar toda a história!

NB Mockito versão: 1.10.19.

Eu sou muito novato no Mockito, então não posso explicar o seguinte comportamento: se houver um especialista por aí que possa melhorar essa resposta, sinta-se à vontade.

O método em questão aqui ,, NÃOgetContentStringValue é e NÃO . final static

Esta linha faz chamar o método original getContentStringValue:

doReturn( "dummy" ).when( im ).getContentStringValue( anyInt(), isA( ScoreDoc.class ));

Esta linha não chama o método original getContentStringValue:

doReturn( "dummy" ).when( im ).getContentStringValue( anyInt(), any( ScoreDoc.class ));

Por razões que não posso responder, o uso isA()faz com que o comportamento pretendido (?) "Não chame método" doReturnfalhe.

Vejamos as assinaturas de método envolvidas aqui: ambos são staticmétodos Matchers. Ambos dizem que o Javadoc retornará null, o que é um pouco difícil de entender. Presumivelmente, o Classobjeto passado quando o parâmetro é examinado, mas o resultado nunca é calculado ou descartado. Dado que isso nullpode representar qualquer classe e que você espera que o método zombado não seja chamado, as assinaturas isA( ... )e any( ... )apenas retornam, em nullvez de um parâmetro genérico * <T>?

De qualquer forma:

public static <T> T isA(java.lang.Class<T> clazz)

public static <T> T any(java.lang.Class<T> clazz)

A documentação da API não fornece nenhuma pista sobre isso. Parece também dizer que a necessidade de tal comportamento "não chame método" é "muito rara". Pessoalmente, uso essa técnica o tempo todo : normalmente, acho que a zombaria envolve algumas linhas que "definem o cenário" ... seguidas chamando um método que "reproduz" a cena no contexto de simulação que você organizou. ... e enquanto você está montando o cenário e os acessórios, a última coisa que você quer é que os atores entrem no palco à esquerda e comecem a representar seus corações ...

Mas isso está muito além do meu salário ... Convido explicações de qualquer sumo sacerdote que passe por Mockito ...

* "parâmetro genérico" é o termo certo?

microfone roedor
fonte
Não sei se isso adiciona clareza ou confunde ainda mais o assunto, mas a diferença entre isA () e any () é que isA realmente faz verificação de tipo, enquanto que qualquer família de métodos () foi criada simplesmente para evitar a conversão de tipos do tipo argumento.
Kevin Welker
@KevinWelker Thanks. E, de fato, os nomes dos métodos não faltam em uma certa qualidade auto-explicativa. No entanto, e de maneira moderada, discuto os gênios designers da Mockito por não documentarem adequadamente. Sem dúvida, preciso ler mais um livro sobre Mockito. PS, na verdade, parece haver muito poucos recursos para ensinar "Mockito intermediário"!
mike roedor
11
A história é que os métodos anyXX foram criados primeiro como uma maneira de lidar apenas com a tipografia. Então, quando foi sugerido que eles adicionassem a verificação de argumento, eles não queriam interromper os usuários da API existente e criaram a família isA (). Sabendo que os métodos any () deveriam ter feito a verificação do tipo o tempo todo, eles atrasaram a alteração desses métodos até introduzirem outras alterações na revisão do Mockito 2.X (que eu ainda não tentei). No 2.x +, os métodos anyX () são aliases para os métodos isA ().
Kevin Welker
Obrigado. Essa é uma resposta essencial para nós, que fazemos várias atualizações de biblioteca ao mesmo tempo, porque o código que costumava ser executado está falhando repentina e silenciosamente.
Dex Stakker
6

Mais um cenário possível que pode causar problemas com espiões é quando você está testando o spring beans (com estrutura de teste de primavera) ou alguma outra estrutura que faz proxy de seus objetos durante o teste .

Exemplo

@Autowired
private MonitoringDocumentsRepository repository

void test(){
    repository = Mockito.spy(repository)
    Mockito.doReturn(docs1, docs2)
            .when(repository).findMonitoringDocuments(Mockito.nullable(MonitoringDocumentSearchRequest.class));
}

No código acima, o Spring e o Mockito tentarão proxy seu objeto MonitoringDocumentsRepository, mas o Spring será o primeiro, o que causará a chamada real do método findMonitoringDocuments. Se depurarmos nosso código logo após colocar um espião no objeto de repositório, ele se parecerá com este no depurador:

repository = MonitoringDocumentsRepository$$EnhancerBySpringCGLIB$$MockitoMock$

@SpyBean para o resgate

Se, em vez disso @Autowired, usarmos a @SpyBeananotação, resolveremos o problema acima, a anotação do SpyBean também injetará o objeto do repositório, mas será primeiro proxyizado pelo Mockito e será semelhante a esse dentro do depurador

repository = MonitoringDocumentsRepository$$MockitoMock$$EnhancerBySpringCGLIB$

e aqui está o código:

@SpyBean
private MonitoringDocumentsRepository repository

void test(){
    Mockito.doReturn(docs1, docs2)
            .when(repository).findMonitoringDocuments(Mockito.nullable(MonitoringDocumentSearchRequest.class));
}
Adrian Kapuscinski
fonte
1

Encontrei mais um motivo para o espião chamar o método original.

Alguém teve a idéia de zombar de uma finalclasse e descobriu sobre MockMaker:

Como isso funciona de maneira diferente do nosso mecanismo atual e este possui limitações diferentes, e como queremos reunir experiência e feedback do usuário, esse recurso precisou ser explicitamente ativado para estar disponível; isso pode ser feito através do mecanismo de extensão mockito, criando o arquivo que src/test/resources/mockito-extensions/org.mockito.plugins.MockMakercontém uma única linha:mock-maker-inline

Fonte: https://github.com/mockito/mockito/wiki/What%27s-new-in-Mockito-2#mock-the-unmockable-opt-in-mocking-of-final-classesmethods

Após mesclar e trazer esse arquivo para minha máquina, meus testes falharam.

Eu apenas tive que remover a linha (ou o arquivo) e spy() funcionou.

Matruskan
fonte
Por esse motivo, no meu caso, eu estava tentando zombar de um método final, mas ele continuava chamando o método real sem uma mensagem de erro clara e confusa.
precisa saber é o seguinte
1

Um pouco atrasado para a festa, mas as soluções acima não funcionaram para mim, então compartilhar meus 0,02 $

Versão Mokcito: 1.10.19

MyClass.java

private int handleAction(List<String> argList, String action)

Test.java

MyClass spy = PowerMockito.spy(new MyClass());

A seguir NÃO funcionou para mim (o método real estava sendo chamado):

1

doReturn(0).when(spy , "handleAction", ListUtils.EMPTY_LIST, new String());

2)

doReturn(0).when(spy , "handleAction", any(), anyString());

3)

doReturn(0).when(spy , "handleAction", null, null);

Seguinte TRABALHADO:

doReturn(0).when(spy , "handleAction", any(List.class), anyString());
tentando aprender
fonte
0

Uma maneira de garantir que um método de uma classe não seja chamado é substituir o método por um fictício.

    WebFormCreatorActivity activity = spy(new WebFormCreatorActivity(clientFactory) {//spy(new WebFormCreatorActivity(clientFactory));
            @Override
            public void select(TreeItem i) {
                log.debug("SELECT");
            };
        });
Geoffrey Ritchey
fonte
-1

Resposta para usuários do scala: Mesmo colocando o doReturnprimeiro não funciona! Veja este post .

Nick Resnick
fonte
esta não é uma resposta
Umpa