Mockito vs EasyMock vs JMockit

Mockito vs EasyMock vs JMockit

1. introduction

1.1. Vue d'ensemble

Dans cet article, nous allons parler democking: ce que c'est, pourquoi l'utiliser et plusieurs exemples de comment se moquer du même cas de test en utilisant certaines des bibliothèques de simulation les plus utilisées pour Java.

Nous commencerons par quelques définitions formelles / semi-formelles des concepts moqueurs; puis nous présenterons le cas sous test, suivrons avec des exemples pour chaque bibliothèque et finirons avec quelques conclusions. Les bibliothèques choisies sontMockito,EasyMock etJMockit.

Si vous sentez que vous connaissez déjà les bases de la raillerie, vous pouvez peut-être passer au point 2 sans lire les trois points suivants.

1.2. Raisons d'utiliser des simulacres

Nous allons commencer à supposer que vous codez déjà en suivant une méthodologie de développement axée sur les tests (TDD, ATDD ou BDD). Ou simplement que vous voulez créer un test pour une classe existante qui s'appuie sur des dépendances pour obtenir ses fonctionnalités.

Dans tous les cas, lors du test unitaire d'une classe, nous voulonstest only its functionality and not that of its dependencies (soit parce que nous faisons confiance à leur implémentation, soit parce que nous allons la tester nous-mêmes).

Pour ce faire, nous devons fournir à l'objet à tester un remplacement que nous pouvons contrôler pour cette dépendance. De cette façon, nous pouvons imposer des valeurs de retour extrêmes, une exception ou simplement réduire les méthodes fastidieuses à une valeur de retour fixe.

Ce remplacement contrôlé est lemock, et il vous aidera à simplifier le codage des tests et à réduire le temps d'exécution des tests.

1.3. Concepts et définition simulés

Voyons quatre définitions à partir d'unarticle écrit par Martin Fowler qui résume les bases que tout le monde devrait connaître sur les simulations:

  • Les objetsDummy sont transmis mais jamais réellement utilisés. Habituellement, ils sont simplement utilisés pour remplir des listes de paramètres.

  • Les objetsFake ont des implémentations fonctionnelles, mais prennent généralement un raccourci qui les rend inadaptés à la production (une base de données en mémoire est un bon exemple).

  • Stubs fournit des réponses standardisées aux appels effectués pendant le test, ne répondant généralement pas du tout à quoi que ce soit en dehors de ce qui est programmé pour le test. Les talons peuvent également enregistrer des informations sur les appels, comme un talon de passerelle de messagerie qui se souvient des messages qu’il a «envoyés», ou peut-être uniquement le nombre de messages qu’il a «envoyés».

  • On parle ici deMocks: des objets préprogrammés avec des attentes qui forment une spécification des appels qu'ils sont censés recevoir.

1.4 To Mock or Not to Mock: That Is the Question

Not everything must be mocked. Parfois, il est préférable de faire un test d'intégration car se moquer de cette méthode / fonctionnalité ne fonctionnerait que pour un bénéfice réel minime. Dans notre cas de test (qui sera montré au point suivant), ce serait tester lesLoginDao.

LesLoginDao utiliseraient une bibliothèque tierce pour l'accès à la base de données, et se moquer de cela ne consisterait qu'à s'assurer que les paramètres ont été préparés pour l'appel, mais nous aurions encore besoin de tester que l'appel renvoie les données que nous voulions.

Pour cette raison, il ne sera pas inclus dans cet exemple (bien que nous puissions écrire à la fois le test unitaire avec des appels simulés pour les appels de bibliothèque tierce ET un test d'intégration avec DBUnit pour tester les performances réelles de la bibliothèque tierce).

2. Cas de test

En gardant à l’esprit tout ce qui se trouve dans la section précédente, proposons un cas de test assez typique et la façon dont nous allons le tester à l’aide de simulacres (quand il est judicieux d’utiliser des simulacres). Cela nous aidera à avoir un scénario commun pour pouvoir ultérieurement comparer les différentes bibliothèques moqueuses.

2.1 Proposed Case

Le cas de test proposé sera le processus de connexion dans une application avec une architecture en couches.

La demande de connexion sera traitée par un contrôleur, qui utilise un service, qui utilise un DAO (qui recherche les informations d'identification de l'utilisateur sur une base de données). Nous n'approfondirons pas trop la mise en œuvre de chaque couche et nous nous concentrerons davantage sur lesinteractions between the components de chaque couche.

De cette façon, nous aurons unLoginController, unLoginService et unLoginDAO. Voyons un diagramme pour clarification:

Test case diagram

2.2 Implementation

Nous allons maintenant suivre la mise en œuvre utilisée pour le scénario de test, afin que nous puissions comprendre ce qui se passe (ou ce qui devrait se passer) lors des tests.

Nous allons commencer par le modèle utilisé pour toutes les opérations,UserForm, qui ne contiendra que le nom et le mot de passe de l'utilisateur (nous utilisons des modificateurs d'accès public pour simplifier) ​​et une méthode getter pour le champusername pour permettre la moquerie pour cette propriété:

public class UserForm {
    public String password;
    public String username;
    public String getUsername(){
        return username;
    }
}

Suivons avecLoginDAO, qui sera dépourvu de fonctionnalités car nous voulons seulement que ses méthodes soient là afin que nous puissions les moquer en cas de besoin:

public class LoginDao {
    public int login(UserForm userForm){
        return 0;
    }
}

LoginDao sera utilisé parLoginService dans sa méthodelogin. LoginService aura également une méthodesetCurrentUser qui retournevoid pour tester cette moquerie.

public class LoginService {
    private LoginDao loginDao;
    private String currentUser;

    public boolean login(UserForm userForm) {
        assert null != userForm;
        int loginResults = loginDao.login(userForm);
        switch (loginResults){
            case 1:
                return true;
            default:
                return false;
        }
    }

    public void setCurrentUser(String username) {
        if(null != username){
            this.currentUser = username;
        }
    }
}

Enfin,LoginController utiliseraLoginService pour sa méthodelogin. Cela comprendra:

  • un cas dans lequel aucun appel au service simulé ne sera fait.

  • un cas dans lequel une seule méthode sera appelée.

  • un cas dans lequel toutes les méthodes seront appelées.

  • un cas dans lequel le lancement d'exception sera testé.

public class LoginController {
    public LoginService loginService;

    public String login(UserForm userForm){
        if(null == userForm){
            return "ERROR";
        }else{
            boolean logged;

            try {
                logged = loginService.login(userForm);
            } catch (Exception e) {
                return "ERROR";
            }

            if(logged){
                loginService.setCurrentUser(userForm.getUsername());
                return "OK";
            }else{
                return "KO";
            }
        }
    }
}

Maintenant que nous avons vu ce que nous essayons de tester, voyons comment nous allons nous en moquer avec chaque bibliothèque.

3. Configuration du test

3.1 Mockito

Pour Mockito, nous utiliserons la version2.8.9.

Le moyen le plus simple de créer et d'utiliser des simulations consiste à utiliser les annotations@Mock et@InjectMocks. Le premier créera un modèle pour la classe utilisée pour définir le champ et le second tentera d'injecter les modèles créés dans le modèle annoté.

Il existe d'autres annotations telles que@Spy qui vous permettent de créer une maquette partielle (une maquette qui utilise l'implémentation normale dans des méthodes non simulées).

Cela étant dit, vous devez appelerMockitoAnnotations.initMocks(this) avant d'exécuter des tests qui utiliseraient lesdits simulacres pour que toute cette «magie» fonctionne. Cela se fait généralement dans une méthode annotée@Before. Vous pouvez également utiliser lesMockitoJUnitRunner.

public class LoginControllerTest {

    @Mock
    private LoginDao loginDao;

    @Spy
    @InjectMocks
    private LoginService spiedLoginService;

    @Mock
    private LoginService loginService;

    @InjectMocks
    private LoginController loginController;

    @Before
    public void setUp() {
        loginController = new LoginController();
        MockitoAnnotations.initMocks(this);
    }
}

3.2 EasyMock

Pour EasyMock, nous utiliserons la version3.4 (Javadoc). Notez qu'avec EasyMock, pour que les simulations commencent à «fonctionner», vous devez appelerEasyMock.replay(mock) sur chaque méthode de test, ou vous recevrez une exception.

Les classes simulées et testées peuvent également être définies via des annotations, mais dans ce cas, au lieu d'appeler une méthode statique pour que cela fonctionne, nous utiliserons lesEasyMockRunner pour la classe de test.

Les mocks sont créés avec l'annotation@Mock et l'objet testé avec celui de@TestSubject (qui obtiendra ses dépendances injectées à partir des mocks créés). L'objet testé doit être créé en ligne.

@RunWith(EasyMockRunner.class)
public class LoginControllerTest {

    @Mock
    private LoginDao loginDao;

    @Mock
    private LoginService loginService;

    @TestSubject
    private LoginController loginController = new LoginController();
}

3.3. JMockit

Pour JMockit, nous utiliserons la version1.24 (Javadoc) car la version 1.25 n’a pas encore été publiée (du moins pendant l’écriture de cet article).

L'installation pour JMockit est aussi simple qu'avec Mockito, à l'exception qu'il n'y a pas d'annotation spécifique pour les simulations partielles (et vraiment pas besoin non plus) et que vous devez utiliserJMockit comme lanceur de test.

Les simulations sont définies en utilisant l'annotation@Injectable (qui ne créera qu'une seule instance fictive) ou avec l'annotation@Mocked (qui créera des simulations pour chaque instance de la classe du champ annoté).

L'instance testée est créée (et ses dépendances simulées injectées) à l'aide de l'annotation@Tested.

@RunWith(JMockit.class)
public class LoginControllerTest {

    @Injectable
    private LoginDao loginDao;

    @Injectable
    private LoginService loginService;

    @Tested
    private LoginController loginController;
}

4. Vérification de l'absence d'appels à simuler

4.1. Mockito

Pour vérifier qu'un simulacre n'a reçu aucun appel dans Mockito, vous disposez de la méthodeverifyZeroInteractions() qui accepte un simulacre.

@Test
public void assertThatNoMethodHasBeenCalled() {
    loginController.login(null);
    Mockito.verifyZeroInteractions(loginService);
}

4.2. EasyMock

Pour vérifier qu’un simulacre n’a reçu aucun appel, vous ne spécifiez tout simplement pas le comportement, vous rejouez le simulacre et, enfin, vous le vérifiez.

@Test
public void assertThatNoMethodHasBeenCalled() {
    EasyMock.replay(loginService);
    loginController.login(null);
    EasyMock.verify(loginService);
}

4.3. JMockit

Pour vérifier qu'une maquette n'a reçu aucun appel, vous ne spécifiez simplement pas les attentes pour cette maquette et effectuez unFullVerifications(mock) pour ladite maquette.

@Test
public void assertThatNoMethodHasBeenCalled() {
    loginController.login(null);
    new FullVerifications(loginService) {};
}

5. Définition des appels de méthode simulés et vérification des appels aux simulations

5.1. Mockito

Pourmocking method calls, vous pouvez utiliserMockito.when(mock.method(args)).thenReturn(value). Ici, vous pouvez renvoyer des valeurs différentes pour plus d'un appel en les ajoutant simplement en tant que paramètres supplémentaires:thenReturn(value1, value2, value-n, …).

Notez que vous ne pouvez pas vous moquer des méthodes de retour vides avec cette syntaxe. Dans ces cas, vous utiliserez une vérification de ladite méthode (comme indiqué à la ligne 11).

Pourverifying calls à une maquette, vous pouvez utiliserMockito.verify(mock).method(args) et vous pouvez également vérifier qu'aucun autre appel n'a été fait à une maquette en utilisantverifyNoMoreInteractions(mock).

Pourverifying args, vous pouvez passer des valeurs spécifiques ou utiliser des matchers prédéfinis commeany(),anyString(),anyInt(). Il y a beaucoup plus de ce type de matchers et même la possibilité de définir vos correspondants que nous verrons dans les exemples suivants.

@Test
public void assertTwoMethodsHaveBeenCalled() {
    UserForm userForm = new UserForm();
    userForm.username = "foo";
    Mockito.when(loginService.login(userForm)).thenReturn(true);

    String login = loginController.login(userForm);

    Assert.assertEquals("OK", login);
    Mockito.verify(loginService).login(userForm);
    Mockito.verify(loginService).setCurrentUser("foo");
}

@Test
public void assertOnlyOneMethodHasBeenCalled() {
    UserForm userForm = new UserForm();
    userForm.username = "foo";
    Mockito.when(loginService.login(userForm)).thenReturn(false);

    String login = loginController.login(userForm);

    Assert.assertEquals("KO", login);
    Mockito.verify(loginService).login(userForm);
    Mockito.verifyNoMoreInteractions(loginService);
}

5.2. EasyMock

Pourmocking method calls, vous utilisezEasyMock.expect(mock.method(args)).andReturn(value).

Pourverifying calls à une maquette, vous pouvez utiliserEasyMock.verify(mock), mais vous devez l'appeleralways after en appelantEasyMock.replay(mock).

Pourverifying args, vous pouvez transmettre des valeurs spécifiques, ou vous avez des correspondants prédéfinis comme isA(Class.class),anyString(),anyInt() et unlot more de ce type de correspondance et encore la possibilité de définir vos matchers.

@Test
public void assertTwoMethodsHaveBeenCalled() {
    UserForm userForm = new UserForm();
    userForm.username = "foo";
    EasyMock.expect(loginService.login(userForm)).andReturn(true);
    loginService.setCurrentUser("foo");
    EasyMock.replay(loginService);

    String login = loginController.login(userForm);

    Assert.assertEquals("OK", login);
    EasyMock.verify(loginService);
}

@Test
public void assertOnlyOneMethodHasBeenCalled() {
    UserForm userForm = new UserForm();
    userForm.username = "foo";
    EasyMock.expect(loginService.login(userForm)).andReturn(false);
    EasyMock.replay(loginService);

    String login = loginController.login(userForm);

    Assert.assertEquals("KO", login);
    EasyMock.verify(loginService);
}

5.3. JMockit

Avec JMockit, vous avez définisteps pour les tests: enregistrer, rejouer et vérifier.

Record est fait dans un nouveau blocExpectations()\{\{}} (dans lequel vous pouvez définir des actions pour plusieurs simulacres),replay se fait simplement en invoquant une méthode de la classe testée (qui devrait en appeler object), etverification est effectué dans un nouveau blocVerifications()\{\{}} (dans lequel vous pouvez définir des vérifications pour plusieurs simulacres).

Pourmocking method calls, vous pouvez utilisermock.method(args); result = value; dans n'importe quel blocExpectations. Ici, vous pouvez renvoyer des valeurs différentes pour plusieurs appels en utilisant simplementreturns(value1, value2, …, valuen); au lieu deresult = value;.

Pourverifying calls à une simulation, vous pouvez utiliser les nouvelles vérifications()\{\{mock.call(value)}} ounew Verifications(mock)\{\{}} pour vérifier chaque appel attendu précédemment défini.

Pourverifying args, vous pouvez transmettre des valeurs spécifiques, ou vous avezpredefined values commeany,anyString,anyLong, et bien d'autres de ce type de valeurs spéciales et encore la possibilité de définir vos matchers (qui doivent être des matchers Hamcrest).

@Test
public void assertTwoMethodsHaveBeenCalled() {
    UserForm userForm = new UserForm();
    userForm.username = "foo";
    new Expectations() {{
        loginService.login(userForm); result = true;
        loginService.setCurrentUser("foo");
    }};

    String login = loginController.login(userForm);

    Assert.assertEquals("OK", login);
    new FullVerifications(loginService) {};
}

@Test
public void assertOnlyOneMethodHasBeenCalled() {
    UserForm userForm = new UserForm();
    userForm.username = "foo";
    new Expectations() {{
        loginService.login(userForm); result = false;
        // no expectation for setCurrentUser
    }};

    String login = loginController.login(userForm);

    Assert.assertEquals("KO", login);
    new FullVerifications(loginService) {};
}

6. Lancer d'exceptions moqueuses

6.1. Mockito

Le lancement d'exceptions peut être simulé en utilisant.thenThrow(ExceptionClass.class) après unMockito.when(mock.method(args)).

@Test
public void mockExceptionThrowin() {
    UserForm userForm = new UserForm();
    Mockito.when(loginService.login(userForm)).thenThrow(IllegalArgumentException.class);

    String login = loginController.login(userForm);

    Assert.assertEquals("ERROR", login);
    Mockito.verify(loginService).login(userForm);
    Mockito.verifyZeroInteractions(loginService);
}

6.2. EasyMock

Le lancement d'exceptions peut être simulé en utilisant.andThrow(new ExceptionClass()) après un appel àEasyMock.expect(…).

@Test
public void mockExceptionThrowing() {
    UserForm userForm = new UserForm();
    EasyMock.expect(loginService.login(userForm)).andThrow(new IllegalArgumentException());
    EasyMock.replay(loginService);

    String login = loginController.login(userForm);

    Assert.assertEquals("ERROR", login);
    EasyMock.verify(loginService);
}

6.3. JMockit

L'exception moqueuse jetant avec JMockito est particulièrement facile. Il suffit de renvoyer une exception à la suite d'un appel de méthode simulé au lieu du retour «normal».

@Test
public void mockExceptionThrowing() {
    UserForm userForm = new UserForm();
    new Expectations() {{
        loginService.login(userForm); result = new IllegalArgumentException();
        // no expectation for setCurrentUser
    }};

    String login = loginController.login(userForm);

    Assert.assertEquals("ERROR", login);
    new FullVerifications(loginService) {};
}

7. Se moquer d'un objet à passer

7.1. Mockito

Vous pouvez également créer une maquette à passer comme argument pour un appel de méthode. Avec Mockito, vous pouvez le faire avec une seule ligne.

@Test
public void mockAnObjectToPassAround() {
    UserForm userForm = Mockito.when(Mockito.mock(UserForm.class).getUsername())
      .thenReturn("foo").getMock();
    Mockito.when(loginService.login(userForm)).thenReturn(true);

    String login = loginController.login(userForm);

    Assert.assertEquals("OK", login);
    Mockito.verify(loginService).login(userForm);
    Mockito.verify(loginService).setCurrentUser("foo");
}

7.2. EasyMock

Les mocks peuvent être créés en ligne avecEasyMock.mock(Class.class). Ensuite, vous pouvez utiliserEasyMock.expect(mock.method()) pour le préparer à l'exécution, en vous rappelant toujours d'appelerEasyMock.replay(mock) avant de l'utiliser.

@Test
public void mockAnObjectToPassAround() {
    UserForm userForm = EasyMock.mock(UserForm.class);
    EasyMock.expect(userForm.getUsername()).andReturn("foo");
    EasyMock.expect(loginService.login(userForm)).andReturn(true);
    loginService.setCurrentUser("foo");
    EasyMock.replay(userForm);
    EasyMock.replay(loginService);

    String login = loginController.login(userForm);

    Assert.assertEquals("OK", login);
    EasyMock.verify(userForm);
    EasyMock.verify(loginService);
}

7.3. JMockit

Pour simuler un objet pour une seule méthode, vous pouvez simplement le transmettre comme paramètre à la méthode de test. Ensuite, vous pouvez créer des attentes comme avec n'importe quel autre simulacre.

@Test
public void mockAnObjectToPassAround(@Mocked UserForm userForm) {
    new Expectations() {{
        userForm.getUsername(); result = "foo";
        loginService.login(userForm); result = true;
        loginService.setCurrentUser("foo");
    }};

    String login = loginController.login(userForm);

    Assert.assertEquals("OK", login);
    new FullVerifications(loginService) {};
    new FullVerifications(userForm) {};
}

8. Correspondance d'arguments personnalisés

8.1. Mockito

Parfois, la correspondance d'arguments pour les appels simulés doit être un peu plus complexe qu'une simple valeur fixe ouanyString(). Pour ce cas, Mockito a sa classe matcher qui est utilisée avecargThat(ArgumentMatcher<>).

@Test
public void argumentMatching() {
    UserForm userForm = new UserForm();
    userForm.username = "foo";
    // default matcher
    Mockito.when(loginService.login(Mockito.any(UserForm.class))).thenReturn(true);

    String login = loginController.login(userForm);

    Assert.assertEquals("OK", login);
    Mockito.verify(loginService).login(userForm);
    // complex matcher
    Mockito.verify(loginService).setCurrentUser(ArgumentMatchers.argThat(
        new ArgumentMatcher() {
            @Override
            public boolean matches(String argument) {
                return argument.startsWith("foo");
            }
        }
    ));
}

8.2. EasyMock

La correspondance d'arguments personnalisés est un peu plus compliquée avec EasyMock car vous devez créer une méthode statique dans laquelle vous créez le matcher réel, puis le rapportez avecEasyMock.reportMatcher(IArgumentMatcher).

Une fois que cette méthode est créée, vous l’utilisez sur votre attente fictive avec un appel à la méthode (comme dans l’exemple en ligne).

@Test
public void argumentMatching() {
    UserForm userForm = new UserForm();
    userForm.username = "foo";
    // default matcher
    EasyMock.expect(loginService.login(EasyMock.isA(UserForm.class))).andReturn(true);
    // complex matcher
    loginService.setCurrentUser(specificArgumentMatching("foo"));
    EasyMock.replay(loginService);

    String login = loginController.login(userForm);

    Assert.assertEquals("OK", login);
    EasyMock.verify(loginService);
}

private static String specificArgumentMatching(String expected) {
    EasyMock.reportMatcher(new IArgumentMatcher() {
        @Override
        public boolean matches(Object argument) {
            return argument instanceof String
              && ((String) argument).startsWith(expected);
        }

        @Override
        public void appendTo(StringBuffer buffer) {
            //NOOP
        }
    });
    return null;
}

8.3. JMockit

La correspondance d'arguments personnalisés avec JMockit se fait avec la méthode spécialewithArgThat(Matcher) (qui reçoit les objetsHamcrest deMatcher).

@Test
public void argumentMatching() {
    UserForm userForm = new UserForm();
    userForm.username = "foo";
    // default matcher
    new Expectations() {{
        loginService.login((UserForm) any);
        result = true;
        // complex matcher
        loginService.setCurrentUser(withArgThat(new BaseMatcher() {
            @Override
            public boolean matches(Object item) {
                return item instanceof String && ((String) item).startsWith("foo");
            }

            @Override
            public void describeTo(Description description) {
                //NOOP
            }
        }));
    }};

    String login = loginController.login(userForm);

    Assert.assertEquals("OK", login);
    new FullVerifications(loginService) {};
}

9. Mocking partiel

9.1. Mockito

Mockito permet de se moquer de façon partielle (une simulation qui utilise l'implémentation réelle au lieu d'appels de méthode simulés dans certaines de ses méthodes) de deux manières.

Vous pouvez soit utiliser.thenCallRealMethod() dans une définition d'appel de méthode fictive normale, soit créer unspy au lieu d'une simulation, auquel cas le comportement par défaut pour cela sera d'appeler l'implémentation réelle dans tous les non- méthodes moquées.

@Test
public void partialMocking() {
    // use partial mock
    loginController.loginService = spiedLoginService;
    UserForm userForm = new UserForm();
    userForm.username = "foo";
    // let service's login use implementation so let's mock DAO call
    Mockito.when(loginDao.login(userForm)).thenReturn(1);

    String login = loginController.login(userForm);

    Assert.assertEquals("OK", login);
    // verify mocked call
    Mockito.verify(spiedLoginService).setCurrentUser("foo");
}

9.2. EasyMock

La simulation partielle se complique également un peu avec EasyMock, car vous devez définir quelles méthodes seront simulées lors de la création de la maquette.

Ceci est fait avecEasyMock.partialMockBuilder(Class.class).addMockedMethod(“methodName”).createMock(). Une fois que cela est fait, vous pouvez utiliser la maquette comme n'importe quelle autre maquette non partielle.

@Test
public void partialMocking() {
    UserForm userForm = new UserForm();
    userForm.username = "foo";
    // use partial mock
    LoginService loginServicePartial = EasyMock.partialMockBuilder(LoginService.class)
      .addMockedMethod("setCurrentUser").createMock();
    loginServicePartial.setCurrentUser("foo");
    // let service's login use implementation so let's mock DAO call
    EasyMock.expect(loginDao.login(userForm)).andReturn(1);

    loginServicePartial.setLoginDao(loginDao);
    loginController.loginService = loginServicePartial;

    EasyMock.replay(loginDao);
    EasyMock.replay(loginServicePartial);

    String login = loginController.login(userForm);

    Assert.assertEquals("OK", login);
    // verify mocked call
    EasyMock.verify(loginServicePartial);
    EasyMock.verify(loginDao);
}

9.3. JMockit

Se moquer partiellement avec JMockit est particulièrement facile. Chaque appel de méthode pour lequel aucun comportement simulé n'a été défini dans unExpectations()\{\{}} utilise l'implémentation «réelle».

Dans ce cas, aucune attente n'étant donnée pourLoginService.login(UserForm), l'implémentation réelle (et l'appel àLoginDAO.login(UserForm)) est effectuée.

@Test
public void partialMocking() {
    // use partial mock
    LoginService partialLoginService = new LoginService();
    partialLoginService.setLoginDao(loginDao);
    loginController.loginService = partialLoginService;

    UserForm userForm = new UserForm();
    userForm.username = "foo";
    // let service's login use implementation so let's mock DAO call
    new Expectations() {{
        loginDao.login(userForm); result = 1;
        // no expectation for loginService.login
        partialLoginService.setCurrentUser("foo");
    }};

    String login = loginController.login(userForm);

    Assert.assertEquals("OK", login);
    // verify mocked call
    new FullVerifications(partialLoginService) {};
    new FullVerifications(loginDao) {};
}

10. Conclusion

Dans cet article, nous avons comparé trois bibliothèques de simulation Java, chacune avec ses points forts et ses inconvénients.

  • Tous les trois sont deseasily configured avec des annotations pour vous aider à définir les simulations et l'objet en cours de test, avec des coureurs pour rendre l'injection simulée aussi indolore que possible.

    • Nous dirions que Mockito gagnerait ici car il a une annotation spéciale pour les simulations partielles, mais JMockit n'en a même pas besoin, alors disons que c'est un lien entre les deux.

  • Tous les trois suivent plus ou moins lesrecord-replay-verify pattern, mais à notre avis, le meilleur pour le faire est JMockit car il vous oblige à les utiliser dans des blocs, donc les tests sont plus structurés.

  • Easiness d'utilisation est important afin que vous puissiez travailler le moins possible pour définir vos tests. JMockit sera l'option choisie pour sa structure toujours identique.

  • Mockito est plus ou moins LE plus connu pour que lescommunity soient plus grands.

  • Devoir appelerreplay à chaque fois que vous souhaitez utiliser une simulation est unno-go clair, nous allons donc mettre un moins pour EasyMock.

  • Consistency/simplicity est également important pour moi. Nous avons adoré la façon de renvoyer les résultats de JMockit, qui est identique pour les résultats «normaux» que pour les exceptions.

Cela étant dit, nous allons choisirJMockit comme une sorte de gagnant même si jusqu'à présent nous avons utiliséMockito car nous avons été captivés par sa simplicité et sa structure fixe et essaiera de l'utiliser à partir de maintenant.

Lesfull implementation de ce tutoriel peuvent être trouvés surthe GitHub project alors n'hésitez pas à le télécharger et à jouer avec.