Como dizer a um objeto simulado do Mockito para retornar algo diferente da próxima vez que for chamado?

202

Então, eu estou criando um objeto simulado como uma variável estática no nível da classe ... Assim, em um teste, quero Foo.someMethod()retornar um determinado valor, enquanto em outro teste, quero que ele retorne um valor diferente. O problema que estou tendo é que parece que preciso reconstruir as zombarias para que isso funcione corretamente. Gostaria de evitar a reconstrução das zombarias e apenas usar os mesmos objetos em cada teste.

class TestClass {

    private static Foo mockFoo;

    @BeforeClass
    public static void setUp() {
        mockFoo = mock(Foo.class);
    }

    @Test
    public void test1() {
        when(mockFoo.someMethod()).thenReturn(0);

        TestObject testObj = new TestObject(mockFoo);

        testObj.bar(); // calls mockFoo.someMethod(), receiving 0 as the value

    }

    @Test
    public void test2() {
        when(mockFoo.someMethod()).thenReturn(1);

        TestObject testObj = new TestObject(mockFoo);

        testObj.bar(); // calls mockFoo.someMethod(), STILL receiving 0 as the value, instead of expected 1.

    }

}

No segundo teste, ainda estou recebendo 0 como o valor quando testObj.bar () é chamado ... Qual é a melhor maneira de resolver isso? Observe que eu sei que eu poderia usar uma simulação diferente Fooem cada teste; no entanto, eu tenho que encadear várias solicitações mockFoo, o que significa que eu teria que fazer o encadeamento em cada teste.

Polaris878
fonte

Respostas:

43

Antes de tudo, não torne a simulação estática. Torne-o um campo privado. Basta colocar sua classe setUp no @Beforenot @BeforeClass. Pode ser um monte, mas é barato.

Em segundo lugar, o jeito que você tem agora é a maneira correta de fazer com que uma simulação retorne algo diferente, dependendo do teste.

shoebox639
fonte
438

Você também pode Stub de chamadas consecutivas (nº 10 em 2.8.9 api). Nesse caso, você usaria várias chamadas thenReturn ou uma chamada thenReturn com vários parâmetros (varargs).

import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import org.junit.Before;
import org.junit.Test;

public class TestClass {

    private Foo mockFoo;

    @Before
    public void setup() {
        setupFoo();
    }

    @Test
    public void testFoo() {
        TestObject testObj = new TestObject(mockFoo);

        assertEquals(0, testObj.bar());
        assertEquals(1, testObj.bar());
        assertEquals(-1, testObj.bar());
        assertEquals(-1, testObj.bar());
    }

    private void setupFoo() {
        mockFoo = mock(Foo.class);

        when(mockFoo.someMethod())
            .thenReturn(0)
            .thenReturn(1)
            .thenReturn(-1); //any subsequent call will return -1

        // Or a bit shorter with varargs:
        when(mockFoo.someMethod())
            .thenReturn(0, 1, -1); //any subsequent call will return -1
    }
}
Tony R
fonte
171
Eu acho que você também pode tirar vantagem do fato de que .thenReturn () usa varargs, para que o código possa ser reduzido para: when (mockFoo.someMethod ()). ThenReturn (0, 1, -1);
Justin Muller
10
@JustinMuller - que vale a pena uma resposta em separado, acho que (ao contrário de um comentário)
Brian Agnew
16
Esta resposta não é a coisa correta a ser feita neste caso. Se você stub esse método para retornar 0 e 1, ficará bem enquanto executar test1e depois test2. Mas pode ser que seu ambiente de integração contínua execute os testes na outra ordem. Ou pode ser que você queira executar test2sozinho, sem executar test1primeiro, caso em que falhará. Os testes de unidade devem sempre ser independentes um do outro; e nunca deve haver uma dependência entre testes individuais ou uma ordem específica de testes. Considerando thenReturndeclarações de encadeamento ...
Dawood ibn Kareem
4
... tem seus usos, assim como o uso de varargs para um único thenReturn, não é uma solução correta neste caso específico. Parece-me que as hordas de promotores aqui provavelmente não conseguiram entender a questão.
Dawood ibn Kareem
2
O próprio JUnit não garantir a ordem teste sem@FixMethodOrder
Roger
29

Para todos que procuram retornar algo e depois para outra chamada, lance a exceção:

    when(mockFoo.someMethod())
            .thenReturn(obj1)
            .thenReturn(obj2)
            .thenThrow(new RuntimeException("Fail"));

ou

    when(mockFoo.someMethod())
            .thenReturn(obj1, obj2)
            .thenThrow(new RuntimeException("Fail"));
Nagy Attila
fonte
19

Ou ainda mais limpo:

when(mockFoo.someMethod()).thenReturn(obj1, obj2);
Pedro H
fonte
2
Essa deve ser a resposta.
Ikthiander
14

Para qualquer pessoa que use o spy () e o doReturn () em vez do método when ():

o que você precisa para retornar objetos diferentes em chamadas diferentes é o seguinte:

doReturn(obj1).doReturn(obj2).when(this.spyFoo).someMethod();

.

Para zombarias clássicas:

when(this.mockFoo.someMethod()).thenReturn(obj1, obj2);

ou com uma exceção sendo lançada:

when(mockFoo.someMethod())
        .thenReturn(obj1)
        .thenThrow(new IllegalArgumentException())
        .thenReturn(obj2, obj3);
fl0w
fonte