Este é um uso apropriado do método de reset do Mockito?

54

Eu tenho um método privado na minha classe de teste que constrói um objeto Bar comumente usado. O construtor Bar chama o método someMethod() no meu objeto ridicularizado:

private @Mock Foo mockedObject; // My mocked object
...

private Bar getBar() {
  Bar result = new Bar(mockedObject); // this calls mockedObject.someMethod()
}

Em alguns dos meus métodos de teste, quero verificar que someMethod também foi invocado por esse teste em particular. Algo como o seguinte:

@Test
public void someTest() {
  Bar bar = getBar();

  // do some things

  verify(mockedObject).someMethod(); // <--- will fail
}

Isso falha, porque o objeto falsificado tinha someMethod chamado duas vezes. Não quero que meus métodos de teste se preocupem com os efeitos colaterais do meu método getBar() , portanto, seria razoável redefinir meu objeto falso no final de getBar() ?

private Bar getBar() {
  Bar result = new Bar(mockedObject); // this calls mockedObject.someMethod()
  reset(mockedObject); // <-- is this OK?
}

Eu pergunto, porque a documentação sugere que a redefinição de objetos simulados é geralmente indicativa de testes ruins. No entanto, isso parece bom para mim.

Alternativa

A escolha alternativa parece estar chamando:

verify(mockedObject, times(2)).someMethod();

que, na minha opinião, obriga cada teste a saber sobre as expectativas de getBar() , para nenhum ganho.

    
por Duncan Jones 25.02.2013 / 11:37
fonte

3 respostas

47

Eu acredito que este é um dos casos em que usar reset() está ok. O teste que você está escrevendo está testando que "algumas coisas" aciona uma única chamada para someMethod() . Escrever a instrução verify() com qualquer número diferente de invocações pode causar confusão.

  • atLeastOnce() permite falsos positivos, o que é uma coisa ruim, pois você quer que seus testes estejam sempre corretos.
  • times(2) impede o falso positivo, mas faz parecer que você está esperando duas invocações, em vez de dizer "eu sei que o construtor adiciona uma". Ainda mais, se algo mudar no construtor para adicionar uma chamada extra, o teste agora tem uma chance de um falso positivo. E remover a chamada faria com que o teste falhasse porque o teste agora está errado, em vez de o que está sendo testado está errado.

Ao usar reset() no método auxiliar, você evita esses dois problemas. No entanto, você precisa ter cuidado para que ele também redefina qualquer stub que você tenha feito, portanto, esteja avisado. O principal motivo pelo qual reset() é desencorajado é evitar

bar = mock(Bar.class);
//do stuff
verify(bar).someMethod();
reset(bar);
//do other stuff
verify(bar).someMethod2();

Isso não é o que o OP está tentando fazer. O OP, estou assumindo, tem um teste que verifica a invocação no construtor. Para este teste, o reset permite isolar esta única ação e seu efeito. Este dos poucos casos com reset() pode ser útil como. As outras opções que não usam tudo têm contras. O fato de que o OP fez este post mostra que ele está pensando sobre a situação e não apenas cegamente utilizando o método de reset.

    
por 27.02.2013 / 15:45
fonte
5

Smart Mockito users hardly use reset feature because they know it could be a sign of poor tests. Normally, you don't need to reset your mocks, just create new mocks for each test method.

Instead of reset() please consider writing simple, small and focused test methods over lengthy, over-specified tests. First potential code smell is reset() in the middle of the test method.

Extraído do mockito docs .

Meu conselho é que você evite usar reset() . Na minha opinião, se você chamar duas vezes para someMethod, isso deve ser testado (talvez seja um acesso ao banco de dados ou outro processo longo com o qual você queira se preocupar).

Se você realmente não se importa com isso, você pode usar:

verify(mockedObject, atLeastOnce()).someMethod();

Note que este último poderia causar um resultado falso, se você chamar someMethod de getBar, e não depois (este é um comportamento errado, mas o teste não falhará).

    
por 27.02.2013 / 09:57
fonte
2

Absolutamente não. Como é frequentemente o caso, a dificuldade que você está tendo em escrever um teste limpo é uma grande bandeira vermelha sobre o design do seu código de produção. Nesse caso, a melhor solução é refatorar seu código para que o construtor de Bar não chame nenhum método.

Os construtores devem construir, não executar lógica. Pegue o valor de retorno do método e passe-o como um parâmetro construtor.

new Bar(mockedObject);

torna-se:

new Bar(mockedObject.someMethod());

Se isso resultar na duplicação dessa lógica em muitos lugares, considere criar um método de fábrica que possa ser testado independentemente do objeto Bar:

public Bar createBar(MockedObject mockedObject) {
    Object dependency = mockedObject.someMethod();
    // ...more logic that used to be in Bar constructor
    return new Bar(dependency);
}

Se essa refatoração for muito difícil, usar reset () é um bom trabalho. Mas vamos ser claros - está indicando que seu código é mal projetado.

    
por 12.10.2017 / 15:52
fonte

Tags