Comment dire à un faux objet Mockito de retourner quelque chose de différent la prochaine fois qu'il sera appelé?

202

Donc, je crée un objet simulé en tant que variable statique au niveau de la classe comme ça ... Dans un test, je veux Foo.someMethod()retourner une certaine valeur, tandis que dans un autre test, je veux qu'il retourne une valeur différente. Le problème que j'ai, c'est qu'il semble que je dois reconstruire les maquettes pour que cela fonctionne correctement. Je voudrais éviter de reconstruire les maquettes et utiliser simplement les mêmes objets dans chaque test.

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.

    }

}

Dans le deuxième test, je reçois toujours 0 comme valeur lorsque testObj.bar () est appelé ... Quelle est la meilleure façon de résoudre ce problème? Notez que je sais que je pourrais utiliser une maquette différente de Foodans chaque test, cependant, je dois chaîner plusieurs demandes mockFoo, ce qui signifie que je devrais faire le chaînage dans chaque test.

Polaris878
la source

Réponses:

43

Tout d'abord, ne rendez pas la maquette statique. Faites-en un champ privé. Mettez simplement votre classe setUp dans le @Beforenot @BeforeClass. Il pourrait être géré en groupe, mais il est bon marché.

Deuxièmement, la façon dont vous l'avez en ce moment est la bonne façon d'obtenir une maquette pour retourner quelque chose de différent en fonction du test.

boîte à chaussures639
la source
439

Vous pouvez également bloquer les appels consécutifs (n ° 10 dans l'api 2.8.9). Dans ce cas, vous utiliseriez plusieurs appels thenReturn ou un appel thenReturn avec plusieurs paramètres (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
la source
171
Je pense que vous pouvez également profiter du fait que .thenReturn () prend des varargs, donc le code peut être raccourci en: when (mockFoo.someMethod ()). ThenReturn (0, 1, -1);
Justin Muller
10
@JustinMuller - cela vaut une réponse distincte, je pense (par opposition à un commentaire)
Brian Agnew
16
Cette réponse n'est pas la bonne chose à faire dans ce cas. Si vous stubez cette méthode pour renvoyer 0 et 1, tout ira bien tant que vous exécutez test1puis test2. Mais il se peut que votre environnement d'intégration continue exécute les tests dans l'autre ordre. Ou il se peut que vous souhaitiez exécuter test2lui-même, sans exécuter en test1premier, auquel cas il échouera. Les tests unitaires doivent toujours être indépendants les uns des autres; et il ne devrait jamais y avoir de dépendance entre les tests individuels, ou une dépendance sur un ordre particulier de tests. Alors que l'enchaînement des thenReturndéclarations ...
Dawood ibn Kareem
4
... a ses utilisations, tout comme l'utilisation de varargs pour un single thenReturn, ce n'est pas une solution correcte dans ce cas particulier. Il me semble que les hordes d'électeurs ici ont très probablement échoué à comprendre la question.
Dawood ibn Kareem
2
Junit lui-même n'assure pas la commande de test sans @FixMethodOrder
Roger
29

Pour tous ceux qui cherchent à renvoyer quelque chose, puis une autre exception de levée d'appel:

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

ou

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

Ou, encore plus propre:

when(mockFoo.someMethod()).thenReturn(obj1, obj2);
Pedro H
la source
2
Cela devrait être la réponse.
Ikthiander
14

Pour toute personne utilisant spy () et doReturn () au lieu de la méthode when ():

ce dont vous avez besoin pour retourner un objet différent sur différents appels est le suivant:

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

.

Pour les simulacres classiques:

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

ou avec une exception levée:

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