web-dev-qa-db-fra.com

Mockito: comment vérifier qu'une 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 que foo a été appelé?

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

Je voudrais faire l'appel de vérification suivant,

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

bar est une instance simulée de Bar.

238
mre

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 le faire), vous disposez de l'accès nécessaire pour effectuer le test.

Exemple d'usine:

Étant donné un cours de 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 de test, 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: Ceci est un exemple de la façon dont TDD peut piloter la conception de votre code.

273
csturtz

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

Existe-t-il un comportement de l'objet Foo (ou moins bon, d'un autre objet de l'environnement) affecté par foo()? Si oui, testez cela. Et si non, que fait la méthode?

18

Si vous ne voulez pas utiliser DI ou Factories. Vous pouvez reformer votre classe de manière 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 constater lorsque vous appelez la méthode de cette manière, 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 souhaitez.

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

J'espère que ça aide.

11
raspacorp

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
  • 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

8
javaPlease42

Je pense que Mockito @InjectMocks est la voie à suivre. 

Selon votre intention, vous pouvez utiliser:

  1. Injection de constructeur
  2. Injection de setter de propriété
  3. Injection de champ

Plus d'infos dans docs

Vous trouverez ci-dessous un exemple d'injection sur le terrain:

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();
    }
}
7
siulkilulki

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 faire en sorte qu’il renvoie une maquette de l’appel au constructeur. Puis faites la vérification sur la maquette. Cela dit, Csturtz est la "bonne" réponse.

Voici le lien vers Construction fictive de nouveaux objets

3
John B

Une autre méthode simple consiste à ajouter une instruction de journal à bar.someMethod (), puis à vérifier que vous pouvez voir ledit message lorsque votre test est exécuté. Voir les exemples ici: Comment faire une assertion JUnit sur un message dans un enregistreur

Cela est particulièrement utile lorsque votre Bar.someMethod () est private.

0
Nestor Milyaev