102 votes

simuler un constructeur avec un paramètre

J'ai une classe comme ci-dessous :

public class A {
    public A(String test) {
        bla bla bla
    }

    public String check() {
        bla bla bla
    }
}

La logique dans le constructeur A(String test) et check() sont les choses dont j'essaie de me moquer. Je veux tous les appels comme : new A($$$any string$$$).check() renvoie une chaîne fictive "test" .

J'ai essayé :

 A a = mock(A.class); 
 when(a.check()).thenReturn("test");

 String test = a.check(); // to this point, everything works. test shows as "tests"

 whenNew(A.class).withArguments(Matchers.anyString()).thenReturn(rk);
 // also tried:
 //whenNew(A.class).withParameterTypes(String.class).withArguments(Matchers.anyString()).thenReturn(rk);

 new A("random string").check();  // this doesn't work

Mais ça n'a pas l'air de fonctionner. new A($$$any string$$$).check() est toujours en train de passer par la logique du constructeur au lieu de récupérer l'objet fantaisie de l'application A .

0 votes

La méthode check() que vous avez simulée fonctionne-t-elle correctement ?

0 votes

@BenGlasser check() fonctionne bien. Mais la fonction whenNew ne semble pas fonctionner du tout. J'ai également mis à jour la description.

98voto

Alban Points 778

Le code que vous avez posté fonctionne pour moi avec la dernière version de Mockito et Powermockito. Peut-être n'avez-vous pas préparé A ? Essayez ceci :

A.java

public class A {
     private final String test;

    public A(String test) {
        this.test = test;
    }

    public String check() {
        return "checked " + this.test;
    }
}

MockA.java

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

@RunWith(PowerMockRunner.class)
@PrepareForTest(A.class)
public class MockA {
    @Test
    public void test_not_mocked() throws Throwable {
        assertThat(new A("random string").check(), equalTo("checked random string"));
    }
    @Test
    public void test_mocked() throws Throwable {
         A a = mock(A.class); 
         when(a.check()).thenReturn("test");
         PowerMockito.whenNew(A.class).withArguments(Mockito.anyString()).thenReturn(a);
         assertThat(new A("random string").check(), equalTo("test"));
    }
}

Les deux tests devraient passer avec mockito 1.9.0, powermockito 1.4.12 et junit 4.8.2.

28 votes

Notez également que si le constructeur est appelé à partir d'une autre classe, il faut l'inclure dans la liste de la rubrique PrepareForTest

0 votes

Quelqu'un a-t-il une idée de la raison pour laquelle nous devrions préparer le self lorsque "PowerMockito.whenNew" est appelé ?

54voto

Ben Glasser Points 466

A ma connaissance, vous ne pouvez pas simuler les constructeurs avec mockito, seulement les méthodes. Mais selon le wiki sur la page de code google de Mockito, il y a un moyen de simuler le comportement du constructeur en créant une méthode dans votre classe qui renvoie une nouvelle instance de cette classe, puis vous pouvez simuler cette méthode. Voici un exemple extrait directement du wiki Mockito :

Patron 1 - utilisation de méthodes en une ligne pour la création d'objets

Pour utiliser le modèle 1 (tester une classe appelée MyClass), vous remplacerez un appel tel que

   Foo foo = new Foo( a, b, c );

avec

   Foo foo = makeFoo( a, b, c );

et écrire une méthode en une ligne

   Foo makeFoo( A a, B b, C c ) { 
        return new Foo( a, b, c );
   }

Il est important de ne pas inclure de logique dans la méthode ; juste la ligne qui crée l'objet objet. La raison de ceci est que la méthode elle-même ne sera jamais être testée en unité.

Lorsque vous testerez la classe, l'objet que vous testerez sera en fait un espion Mockito sera en fait un espion de Mockito, avec cette méthode surchargée, pour retourner un mock. Ce que vous testez n'est donc pas la classe elle-même, mais une mais une version très légèrement modifiée de celle-ci.

Votre classe de test peut contenir des membres comme

  @Mock private Foo mockFoo;
  private MyClass toTest = spy(new MyClass());

Enfin, dans votre méthode de test, vous simulez l'appel à makeFoo avec une ligne comme

  doReturn( mockFoo )
      .when( toTest )
      .makeFoo( any( A.class ), any( B.class ), any( C.class ));

Vous pouvez utiliser des matchers plus spécifiques que any() si vous voulez vérifier les arguments qui sont passés au constructeur.

Si vous souhaitez simplement renvoyer un objet fantaisie de votre classe, je pense que cela devrait fonctionner pour vous. Dans tous les cas, vous pouvez lire plus sur la création d'objets fantaisie ici :

http://code.google.com/p/mockito/wiki/MockingObjectCreation

25 votes

+1, je n'aime pas le fait que je doive ajuster mon code source pour le rendre plus convivial pour les mockitos. Merci pour le partage.

23 votes

Il n'est jamais mauvais d'avoir un code source plus testable, ou d'éviter les anti-modèles de testabilité lorsque vous écrivez votre code. Si vous écrivez du code source qui est plus testable, il est automatiquement plus facile à maintenir. Isoler vos appels de constructeurs dans leurs propres méthodes n'est qu'un moyen d'y parvenir.

3 votes

Écrire du code testable, c'est bien. Être obligé de redessiner la classe A pour pouvoir écrire des tests pour la classe B, qui dépend de A, parce que A a une dépendance codée en dur sur C, c'est... moins bien. Oui, le code sera meilleur à la fin, mais combien de classes vais-je devoir redessiner pour pouvoir finir d'écrire un test ?

26voto

MevlütÖzdemir Points 750

Avec Mockito, vous pouvez utiliser withSettings() . Par exemple, si le CounterService a nécessité 2 dépendances, vous pouvez les passer comme un mock :

 UserService userService = Mockito.mock(UserService.class);
 SearchService searchService = Mockito.mock(SearchService.class);   
 CounterService counterService = Mockito.mock(CounterService.class, withSettings().useConstructor(userService, searchService));

2 votes

A mon avis, la réponse la plus simple et la meilleure. Merci.

0 votes

S'il y a beaucoup de paramètres, ce sera désordonné.

0 votes

@Nayan Je suis d'accord. Mais si vous avez beaucoup de paramètres/dépendances dans vos classes, alors il semble que quelque chose d'autre ne va pas.

12voto

user666 Points 622

Sans utiliser Powermock .... Voir l'exemple ci-dessous basé sur la réponse de Ben Glasser, car il m'a fallu un certain temps pour le comprendre... J'espère que cela vous fera gagner du temps...

Classe d'origine :

public class AClazz {

    public void updateObject(CClazz cClazzObj) {
        log.debug("Bundler set.");
        cClazzObj.setBundler(new BClazz(cClazzObj, 10));
    } 
}

Classe modifiée :

@Slf4j
public class AClazz {

    public void updateObject(CClazz cClazzObj) {
        log.debug("Bundler set.");
        cClazzObj.setBundler(getBObject(cClazzObj, 10));
    }

    protected BClazz getBObject(CClazz cClazzObj, int i) {
        return new BClazz(cClazzObj, 10);
    }
 }

Classe d'essai

public class AClazzTest {

    @InjectMocks
    @Spy
    private AClazz aClazzObj;

    @Mock
    private CClazz cClazzObj;

    @Mock
    private BClazz bClassObj;

    @Before
    public void setUp() throws Exception {
        Mockito.doReturn(bClassObj)
               .when(aClazzObj)
               .getBObject(Mockito.eq(cClazzObj), Mockito.anyInt());
    }

    @Test
    public void testConfigStrategy() {
        aClazzObj.updateObject(cClazzObj);

        Mockito.verify(cClazzObj, Mockito.times(1)).setBundler(bClassObj);
    }
}

0 votes

Que peut-on faire dans les cas où getBObject est private au lieu d'être protected

6voto

rieckpil Points 1620

En commençant par version 3.5.0 de Mockito et en utilisant le InlineMockMaker vous pouvez maintenant vous moquer des constructions d'objets :

 try (MockedConstruction mocked = mockConstruction(A.class)) {
   A a = new A();
   when(a.check()).thenReturn("bar");
 }

A l'intérieur de la try-with-resources construire tous les constructions d'objets renvoient un objet fantaisie.

0 votes

En quoi cela diffère-t-il de A a = mock(A.class) ? De plus, je ne pense pas que cela réponde à la question sur les paramètres.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X