Mockito: comment vérifier que la méthode a été appelée sur un objet créé dans une méthode?

322

Je suis nouveau chez Mockito.

Étant donné la classe ci-dessous, comment puis-je utiliser Mockito pour vérifier qu'il a someMethodété invoqué exactement une fois après avoir fooété invoqué?

public class Foo
{
    public void foo(){
        Bar bar = new Bar();
        bar.someMethod();
    }
}

Je souhaite effectuer l'appel de vérification suivant,

verify(bar, times(1)).someMethod();

barest une instance moquée de Bar.

mre
la source
2
stackoverflow.com/questions/6520242/… - Mais je ne veux pas utiliser PowerMock.
mre
Modifiez l'API ou PowerMock. Un des deux.
John B
Comment couvrir quelque chose comme ça ?? public void start synchronisé (BundleContext bundleContext) lève Exception {BundleContext bc = bundleContext; logger.info ("DÉMARRAGE DU GROUPE DE SERVICE HTTP"); this.tracker = new ServiceTracker (bc, HttpService.class.getName (), null) {@Override public Object addService (ServiceReference serviceRef) {httpService = (HttpService) super.addingService (serviceRef); registerServlets (); return httpService; }}}
ShAkKiR

Réponses:

366

Injection de dépendance

Si vous injectez l'instance Bar, ou une fabrique utilisée pour créer l'instance Bar (ou l'une des 483 autres façons de procéder), vous aurez l'accès nécessaire pour effectuer le test.

Exemple d'usine:

Étant donné une classe Foo écrite comme ceci:

public class Foo {
  private BarFactory barFactory;

  public Foo(BarFactory factory) {
    this.barFactory = factory;
  }

  public void foo() {
    Bar bar = this.barFactory.createBar();
    bar.someMethod();
  }
}

dans votre méthode de test, vous pouvez injecter une BarFactory comme ceci:

@Test
public void testDoFoo() {
  Bar bar = mock(Bar.class);
  BarFactory myFactory = new BarFactory() {
    public Bar createBar() { return bar;}
  };

  Foo foo = new Foo(myFactory);
  foo.foo();

  verify(bar, times(1)).someMethod();
}

Bonus: Ceci est un exemple de la façon dont TDD peut piloter la conception de votre code.

csturtz
la source
6
Existe-t-il un moyen de le faire sans modifier la classe pour les tests unitaires?
mre
6
Bar bar = mock(Bar.class)au lieu deBar bar = new Bar();
John B
7
pas que je sache. mais, je ne suggère pas que vous modifiez la classe uniquement pour les tests unitaires. Il s'agit vraiment d'une conversation sur le code propre et le SRP. Ou .. est-ce la responsabilité de la méthode foo () dans la classe Foo de construire un objet Bar. Si la réponse est oui, alors c'est un détail d'implémentation et vous ne devriez pas vous soucier de tester spécifiquement l'interaction (reportez-vous à la réponse de @ Michael). Si la réponse est non, alors vous modifiez la classe parce que votre difficulté à tester est un drapeau rouge que votre conception a besoin d'une petite amélioration (d'où le bonus que j'ai ajouté sur la façon dont TDD conduit la conception).
csturtz
3
Pouvez-vous passer un "vrai" objet à "vérifier" de Mockito?
John B
4
Vous pouvez également vous moquer de l'usine: BarFactory myFactory = mock(BarFactory.class); when(myFactory.createBar()).thenReturn(bar);
levsa
18

La réponse classique est: "Vous ne le faites pas." Vous testez l'API publique de Foo, pas ses internes.

Y a-t-il un comportement de l' Fooobjet (ou, moins bien, un autre objet dans l'environnement) qui est affecté par foo()? Si oui, testez cela. Et sinon, que fait la méthode?

Michael Brewer-Davis
la source
4
Alors, que feriez-vous réellement ici? L'API publique de Foois public void foo(), où les éléments internes sont uniquement liés à la barre.
behelit
15
Tester uniquement l'API publique est correct, jusqu'à ce qu'il y ait de véritables bogues avec des effets secondaires qui nécessitent des tests. Par exemple, vérifier qu'une méthode privée ferme correctement ses connexions HTTP est exagéré jusqu'à ce que vous découvriez que la méthode privée ne ferme pas correctement ses connexions et cause ainsi un problème majeur. À ce stade, Mockito et verify()devenez très utiles en effet, même si vous n'adorez plus au saint autel des tests d'intégration.
Dawngerpony
@DuffJ Je n'utilise pas Java, mais cela ressemble à quelque chose que votre compilateur ou votre outil d'analyse de code devrait détecter.
user247702
3
Je suis d'accord avec DuffJ, alors que la programmation fonctionnelle est amusante, il arrive un moment où votre code interagit avec le monde extérieur. Peu importe que vous l'appeliez "internes", "effets secondaires" ou "fonctionnalité", vous voulez certainement tester cette interaction: si elle se produit et si elle se produit le nombre de fois correct et avec les arguments corrects. @Stijn: cela pourrait avoir été un mauvais exemple (mais si plusieurs connexions devaient être ouvertes, et seulement certaines d'entre elles fermées, cela devient intéressant). Un meilleur exemple serait de vérifier la météo, les données correctes auraient été envoyées via la connexion.
Andras Balázs Lajtha
13

Si vous ne souhaitez pas utiliser DI ou les usines. Vous pouvez remanier votre classe d'une manière un peu délicate:

public class Foo {
    private Bar bar;

    public void foo(Bar bar){
        this.bar = (bar != null) ? bar : new Bar();
        bar.someMethod();
        this.bar = null;  // for simulating local scope
    }
}

Et votre classe de test:

@RunWith(MockitoJUnitRunner.class)
public class FooTest {
    @Mock Bar barMock;
    Foo foo;

    @Test
    public void testFoo() {
       foo = new Foo();
       foo.foo(barMock);
       verify(barMock, times(1)).someMethod();
    }
}

Ensuite, la classe qui appelle votre méthode foo le fera comme ceci:

public class thirdClass {

   public void someOtherMethod() {
      Foo myFoo = new Foo();
      myFoo.foo(null);
   }
}

Comme vous pouvez le voir lorsque vous appelez la méthode de cette façon, vous n'avez pas besoin d'importer la classe Bar dans une autre classe qui appelle votre méthode foo, ce qui est peut-être quelque chose que vous voulez.

Bien sûr, l'inconvénient est que vous autorisez l'appelant à définir l'objet Bar.

J'espère que ça aide.

raspacorp
la source
3
Je pense que c'est un anti-modèle. Les dépendances doivent être injectées, point final. Autoriser une dépendance éventuellement injectée uniquement à des fins de test, c'est éviter intentionnellement d'améliorer le code et tester intentionnellement quelque chose de différent du code qui s'exécute en production. Ce sont deux choses horribles et horribles à faire.
ErikE
8

Solution pour votre exemple de code en utilisant PowerMockito.whenNew

  • mockito-all 1.10.8
  • powermock-core 1.6.1
  • powermock-module-junit4 1.6.1
  • powermock-api-mockito 1.6.1
  • juin 4.12

FooTest.java

package foo;

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

//Both @PrepareForTest and @RunWith are needed for `whenNew` to work 
@RunWith(PowerMockRunner.class)
@PrepareForTest({ Foo.class })
public class FooTest {

    // Class Under Test
    Foo cut;

    @Mock
    Bar barMock;

    @Before
    public void setUp() throws Exception {
        cut = new Foo();

    }

    @After
    public void tearDown() {
        cut = null;

    }

    @Test
    public void testFoo() throws Exception {

        // Setup
        PowerMockito.whenNew(Bar.class).withNoArguments()
                .thenReturn(this.barMock);

        // Test
        cut.foo();

        // Validations
        Mockito.verify(this.barMock, Mockito.times(1)).someMethod();

    }

}

Sortie JUnit Sortie JUnit

javaPlease42
la source
8

Je pense que Mockito @InjectMocksest la voie à suivre.

Selon votre intention, vous pouvez utiliser:

  1. Injection constructeur
  2. Injection de définition de propriété
  3. Injection sur le terrain

Plus d'informations dans les documents

Voici un exemple avec injection de champ:

Des classes:

public class Foo
{
    private Bar bar = new Bar();

    public void foo() 
    {
        bar.someMethod();
    }
}

public class Bar
{
    public void someMethod()
    {
         //something
    }
}

Tester:

@RunWith(MockitoJUnitRunner.class)
public class FooTest
{
    @Mock
    Bar bar;

    @InjectMocks
    Foo foo;

    @Test
    public void FooTest()
    {
        doNothing().when( bar ).someMethod();
        foo.foo();
        verify(bar, times(1)).someMethod();
    }
}
siulkilulki
la source
3

Oui, si vous voulez / devez vraiment le faire, vous pouvez utiliser PowerMock. Cela devrait être considéré comme un dernier recours. Avec PowerMock, vous pouvez lui faire renvoyer une maquette de l'appel au constructeur. Faites ensuite la vérification sur la maquette. Cela dit, csturtz est la «bonne» réponse.

Voici le lien vers Mock construction de nouveaux objets

John B
la source