Simuler un constructeur avec un paramètre

89

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 ce check()sont les choses que j'essaie de me moquer. Je veux des appels comme: new A($$$any string$$$).check()renvoie une chaîne factice "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 cela ne semble pas fonctionner. new A($$$any string$$$).check()passe toujours par la logique du constructeur au lieu de récupérer l'objet fictif de A.

Shengjie
la source
votre méthode check () simulée fonctionne-t-elle correctement?
Ben Glasser
@BenGlasser check () fonctionne bien. Just the whenNew ne semble pas fonctionner du tout. J'ai également mis à jour la description.
Shengjie

Réponses:

93

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

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 réussir avec mockito 1.9.0, powermockito 1.4.12 et junit 4.8.2

Alban
la source
24
Notez également que si le constructeur est appelé depuis une autre classe, incluez-le dans la liste dansPrepareForTest
Jeff E
Quelqu'un a une idée pourquoi devrions-nous préparer le soi lorsque "PowerMockito.whenNew" est appelé?
udayanga
50

À ma connaissance, vous ne pouvez pas vous moquer des constructeurs avec mockito, uniquement des méthodes. Mais selon le wiki sur la page de code Google de Mockito, il existe un moyen de se moquer du comportement du constructeur en créant une méthode dans votre classe qui retourne une nouvelle instance de cette classe. alors vous pouvez simuler cette méthode. Ci-dessous un extrait directement du wiki Mockito :

Modèle 1 - utilisation de méthodes sur une ligne pour la création d'objets

Pour utiliser le modèle 1 (tester une classe appelée MyClass), vous remplacez un appel comme

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

avec

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

et écrivez une méthode en une ligne

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

Il est important que vous n'incluiez aucune logique dans la méthode; juste la seule ligne qui crée l'objet. La raison en est que la méthode elle-même ne sera jamais testée unitaire.

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

Votre classe de test peut contenir des membres tels que

  @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 correspondances plus spécifiques que any () si vous souhaitez vérifier les arguments qui sont passés au constructeur.

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

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

Ben Glasser
la source
21
+1, je n'aime pas le fait que je doive ajuster mon code source pour le rendre plus convivial pour les mockito. Merci pour le partage.
Shengjie
22
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 une source plus testable, elle est automatiquement plus maintenable. Isoler vos appels de constructeur dans leurs propres méthodes n'est qu'un moyen d'y parvenir.
Dawood ibn Kareem
1
L'écriture de code testable est une bonne chose. Être obligé de repenser la classe A pour que je puisse é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, me semble ... moins bien. Oui, le code sera meilleur à la fin, mais combien de classes vais-je finir par refondre pour pouvoir finir d'écrire un test?
Mark Wood
@MarkWood d'après mon expérience, les expériences de test maladroites sont généralement le signe d'un défaut de conception. IRL si vous testez des constructeurs, votre code vous crie probablement pour une usine ou une injection de dépendances. Si vous suivez des modèles de conception typiques pour ces deux cas, votre code devient beaucoup plus facile à tester et à utiliser en général. Si vous testez des constructeurs parce que vous avez beaucoup de logique, vous avez probablement besoin d'une couche de polymorphisme, ou vous pouvez déplacer cette logique vers une méthode d'initialisation.
Ben Glasser
12

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 permet de gagner du temps ...

Classe originale:

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);
    }
}
utilisateur666
la source
6

Avec mockito, vous pouvez utiliser withSettings (), par exemple si le CounterService nécessitait 2 dépendances, vous pouvez les passer comme un simulacre:

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

MevlütÖzdemir
la source
À mon avis, la réponse la plus simple et la meilleure. Merci.
Eldon
4

Mockito a des limites pour tester les méthodes finales, statiques et privées.

avec la bibliothèque de test jMockit, vous pouvez faire quelques trucs très faciles et simples comme ci-dessous:

Constructeur fictif d'une classe java.io.File:

new MockUp<File>(){
    @Mock
    public void $init(String pathname){
        System.out.println(pathname);
        // or do whatever you want
    }
};
  • le nom du constructeur public doit être remplacé par $ init
  • les arguments et exceptions lancés restent les mêmes
  • le type de retour doit être défini comme void

Simulez une méthode statique:

  • supprimer statique de la signature fictive de la méthode
  • la signature de la méthode reste la même sinon
Amit Kaneria
la source