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

Je suis nouveau à Mockito.

étant donné la classe ci-dessous, Comment puis-je utiliser Mockito pour vérifier que someMethod a été invoqué exactement une fois après foo a été invoqué?

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

j'aimerais faire l'appel de vérification suivant,

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

bar est une instance moquée de Bar .

213
demandé sur mre 2012-03-23 19:09:31

7 réponses

Injection De Dépendance

si vous injectez l'instance de la barre, ou une usine qui est utilisée pour créer l'instance de la barre (ou l'une des 483 autres façons de le faire), vous aurez l'accès nécessaire pour effectuer le test.

Exemple D'Usine:

donné une classe Foo écrit 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 d'essai vous pouvez injecter un 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: C'est un exemple de la façon dont TDD peut piloter la conception de votre code.

249
répondu csturtz 2017-06-13 13:40:28

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'objet Foo (ou, moins bon, un autre objet dans l'environnement) qui est affecté par foo() ? Si oui, test. Et si non, Que fait la méthode?

18
répondu Michael Brewer-Davis 2012-03-23 15:16:37

si vous ne voulez pas utiliser DI ou des 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 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();
    }
}

alors 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 en appelant 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 qui est peut-être quelque chose que vous voulez.

bien sûr, l'inconvénient est que vous permettez à l'appelant de régler l'objet de barre.

J'espère que ça aidera.

10
répondu raspacorp 2015-08-01 00:41:13

Solution pour votre exemple de code en utilisant PowerMockito.whenNew

  • mockito-tous 1.10.8
  • powermock-core 1.6.1
  • powermock-module-junit4 1.6.1
  • powermock-api-mockito 1.6.1
  • junit 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();

    }

}

JUnit Output JUnit Output

6
répondu javaPlease42 2016-04-21 20:21:27

je pense que Mockito @InjectMocks est le chemin à parcourir.

selon votre intention Vous pouvez utiliser:

  1. injection du constructeur
  2. propriété setter injection
  3. "151980920 Champ" injection

plus d'information dans docs

ci-dessous est un exemple avec injection de champ:

Classes:

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

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

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

Test:

@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();
    }
}
6
répondu siulkilulki 2016-06-20 12:36:37

Oui, si vous voulez vraiment / devez le faire, vous pouvez utiliser PowerMock. Cela devrait être considéré comme un dernier recours. Avec PowerMock vous pouvez le faire retourner une moquerie de l'appel au constructeur. Puis faire la vérification sur la moquerie. Cela dit, csturtz est la "bonne" réponse.

voici le lien vers construction simulée de nouveaux objets

3
répondu John B 2012-03-23 16:50:38

une autre façon simple serait d'ajouter une déclaration de log à la barre.someMethod () et ensuite s'assurer que vous pouvez voir ledit message lorsque votre test exécuté, voir les exemples ici: Comment faire une JUnit affirmer sur un message dans un logger

qui est particulièrement pratique quand votre Bar.someMethod () est private .

0
répondu Nestor Milyaev 2018-09-26 11:40:50