web-dev-qa-db-fra.com

Utiliser Mockito pour remplacer et exécuter des méthodes de test

J'ai récemment posé quelques questions concernant jUnit et Mockito et je lutte toujours pour en comprendre le sens. Les tutoriels sont tous des exemples très simples. Je me bats donc pour adapter mes scénarios de test à mon travail.

J'essaie actuellement de rédiger des scénarios de test pour une méthode que j'ai dans l'un de mes agents dans une application Web. La méthode interagit avec quelques autres méthodes à l'intérieur de l'agent pour valider certains objets. Je veux juste tester cette méthode en ce moment.

Voici ce que j'ai essayé de faire:

  1. Créez un objet Mockito de mon agent comme suit:

    MyProcessingAgent mockMyAgent = Mockito.mock(MyProcessingAgent.class);

  2. Installez les stubs (j'espère le bon terme) en utilisant Mockito.when like so:

    Mockito.when(mockMyAgent.otherMethod(Mockito.any(arg1)).thenReturn(requiredReturnArg);

  3. Essayez d’exécuter ma méthode comme suit:

    List myReturnValue = mockMyAgent.methodThatNeedsTestCase();

Je m'attendais à des choses dans myReturnValue, mais j'ai reçu 0 à la place, alors j'ai essayé de déboguer. Lorsque j'appelle la méthode, elle ne s'exécute jamais. J'ai un point de débogage à la première ligne de la méthode qui n'est jamais touché.

Si je veux exécuter le code dans une méthode d'une classe, mais que d'autres méthodes de la classe (celles qui tentent d'interagir avec des bases de données du monde extérieur) soient forcées, elles renverront des valeurs falsifiées. Est-ce possible avec Mockito?

Il semble que ma méthode d’approche actuelle n’est pas un style de test correct, mais je ne sais pas comment aller de l’avant. Puis-je me moquer de ma classe et avoir une méthode exécutée comme d'habitude alors que d'autres méthodes sont écrites pour renvoyer mes valeurs données afin que je n'ai pas à gérer l'accès aux données lors du test de cette méthode?

41
Kyle

Vous confondez une Mock avec une Spy.

Dans une fausse toutes les méthodes sont écrites et retournent des "types de retour intelligents". Cela signifie que l'appel d'une méthode sur une classe simulée ne ne fera rien à moins que vous ne spécifiiez un comportement.

Dans un espion, la fonctionnalité d'origine de la classe est toujours présente, mais vous pouvez valider les appels de méthode dans un espion et remplacer le comportement de la méthode.

Ce que tu veux c'est

MyProcessingAgent mockMyAgent = Mockito.spy(MyProcessingAgent.class);

Un exemple rapide:

static class TestClass {

    public String getThing() {
        return "Thing";
    }

    public String getOtherThing() {
        return getThing();
    }
}

public static void main(String[] args) {
    final TestClass testClass = Mockito.spy(new TestClass());
    Mockito.when(testClass.getThing()).thenReturn("Some Other thing");
    System.out.println(testClass.getOtherThing());
}

La sortie est:

Some Other thing

NB: Vous devriez vraiment essayer de vous moquer des dépendances pour la classe en cours de test pas la classe elle-même.

67
Boris the Spider

Donc, l'idée de se moquer de la classe sous test est une anathima à tester la pratique. Vous ne devriez pas faire cela. Parce que vous l'avez fait, votre test entre dans les cours moqueurs de Mockito et non dans votre classe en cours de test.

L'espionnage ne fonctionnera pas non plus, car cela fournit uniquement un wrapper/proxy autour de la classe espionnée. Une fois que l'exécution est à l'intérieur de la classe, elle ne passera pas par le proxy et ne touchera donc pas l'espion. UPDATE: bien que je pense que cela soit vrai pour les mandataires du printemps, il semble que ce ne soit pas le cas pour les espions Mockito. J'ai mis en place un scénario où la méthode m1() appelle m2(). J'aperçois l'objet et remplace m2() en doNothing. Lorsque j'invoque m1() dans mon test, m2() de la classe n'est pas atteint. Mockito invoque le stub. Donc, utiliser un espion pour accomplir ce qui est demandé est possible. Cependant, je voudrais réitérer que je considérerais cela comme une mauvaise pratique (IMHO).

Vous devriez vous moquer de toutes les classes dont dépend la classe sous test. Cela vous permettra de contrôler le comportement des méthodes appelées par la méthode testée en contrôlant la classe invoquée par ces méthodes.

Si votre classe crée des instances d'autres classes, envisagez d'utiliser des fabriques.

6
John B

Vous l'avez presque. Le problème est que la Classe en cours de test (CUT) n'est pas construite pour les tests unitaires - elle n'a pas (vraiment} été TDD 'd.

Pensez-y comme ça…

  • J'ai besoin de tester une fonction d'une classe - appelons-la myFunction
  • Cette fonction appelle une fonction sur une autre classe/service/base de données
  • Cette fonction appelle également une autre méthode sur le CUT

Dans le test unitaire

  • Devrait créer un COUPÉ ou @Spy concret dessus
  • Vous pouvez @Mock tous les autres classes/services/bases de données (c.-à-d. Dépendances externes) 
  • Vous pourriez tronquer l'autre fonction appelée dans le CUT mais ce n'est pas vraiment comment tester (unité} _ _

Pour éviter d'exécuter du code que vous ne testez pas strictement, vous pouvez le résumer en quelque chose qui peut être @Mocked.

Dans cet exemple simple, {très} _, une fonction qui crée un objet sera difficile à tester

public void doSomethingCool(String foo) {
    MyObject obj = new MyObject(foo);

    // can't do much with obj in a unit test unless it is returned
}

Mais une fonction qui utilise un service pour obtenir MyObject est facile à tester, car nous avons résumé le difficile/impossible de tester le code en quelque chose qui rend cette méthode testable.

public void doSomethingCool(String foo) {
    MyObject obj = MyObjectService.getMeAnObject(foo);
}

comme MyObjectService peut être simulé et également vérifié que .getMeAnObject () est appelé avec la variable foo.

4
andyb

RÉPONSE COURTE

Comment faire dans votre cas:

int argument = 5; // example with int but could be another type
Mockito.when(mockMyAgent.otherMethod(Mockito.anyInt()).thenReturn(requiredReturnArg(argument));

LONGUE RÉPONSE

En fait, ce que vous voulez faire est possible, du moins avec Java 8. Peut-être n’avez-vous pas reçu cette réponse, parce que j’utilise Java 8 qui permet cela et que cette question est antérieure à la publication de Java 8 (qui permet de passer des fonctions , pas seulement des valeurs à d’autres fonctions).

Simulons un appel à une requête DataBase. Cette requête renvoie toutes les lignes de HotelTable qui ont FreeRoms = X et StarNumber = Y . Ce que j'attends des tests, c’est que cette requête restitue une liste d’hôtels différents: chaque hôtel retourné a la même valeur X et Y. , tandis que les autres valeurs et je les déciderai en fonction de mes besoins. L'exemple suivant est simple mais vous pouvez bien sûr le rendre plus complexe.

Je crée donc une fonction qui donnera des résultats différents mais tous ont FreeRoms = X et StarNumber = Y.

static List<Hotel> simulateQueryOnHotels(int availableRoomNumber, int starNumber) {
    ArrayList<Hotel> HotelArrayList = new ArrayList<>();
    HotelArrayList.add(new Hotel(availableRoomNumber, starNumber, Rome, 1, 1));
    HotelArrayList.add(new Hotel(availableRoomNumber, starNumber, Krakow, 7, 15));
    HotelArrayList.add(new Hotel(availableRoomNumber, starNumber, Madrid, 1, 1));
    HotelArrayList.add(new Hotel(availableRoomNumber, starNumber, Athens, 4, 1));

    return HotelArrayList;
}

Peut-être que Spy est meilleur (essayez s'il vous plaît), mais je l'ai fait sur un cours ridiculisé. Voici comment je fais (notez les valeurs anyInt ()):

//somewhere at the beginning of your file with tests...
@Mock
private DatabaseManager mockedDatabaseManager;

//in the same file, somewhere in a test...
int availableRoomNumber = 3;
int starNumber = 4;
// in this way, the mocked queryOnHotels will return a different result according to the passed parameters
when(mockedDatabaseManager.queryOnHotels(anyInt(), anyInt())).thenReturn(simulateQueryOnHotels(availableRoomNumber, starNumber));
0
fresko