Mockito: come verificare il metodo è stato chiamato su un oggetto creato all'interno di un metodo?


322

Sono nuovo di Mockito.

Data la classe seguente, come posso usare Mockito per verificare che sia someMethodstato invocato esattamente una volta dopo che è foostato invocato?

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

Vorrei effettuare la seguente chiamata di verifica,

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

dove barè un'istanza derisa di Bar.


2
stackoverflow.com/questions/6520242/… - Ma non voglio usare PowerMock.
martedì

Modifica l'API o PowerMock. Uno dei due.
Giovanni B,

Come coprire qualcosa del genere ?? inizio vuoto sincronizzato pubblico (BundleContext bundleContext) genera l'eccezione {BundleContext bc = bundleContext; logger.info ("STARTING HTTP SERVICE BUNDLE"); this.tracker = new ServiceTracker (bc, HttpService.class.getName (), null) {@Override Public Object additionService (ServiceReference serviceRef) {httpService = (HttpService) super.addingService (serviceRef); registerServlets (); restituire httpService; }}}
ShAkKiR

Risposte:


366

Iniezione di dipendenza

Se si inietta l'istanza Bar o una factory utilizzata per creare l'istanza Bar (o uno degli altri 483 modi per farlo), si avrebbe l'accesso necessario per eseguire il test.

Esempio di fabbrica:

Data una lezione Foo scritta in questo modo:

public class Foo {
  private BarFactory barFactory;

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

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

nel tuo metodo di test puoi iniettare una BarFactory in questo modo:

@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: questo è un esempio di come TDD può guidare la progettazione del tuo codice.


6
C'è un modo per farlo senza modificare la classe per i test unitari?
martedì

6
Bar bar = mock(Bar.class)anzichéBar bar = new Bar();
John B

7
non che ne sia consapevole. ma non sto suggerendo di modificare la classe solo per i test unitari. Questa è davvero una conversazione su codice pulito e SRP. Oppure ... è responsabilità del metodo foo () nella classe Foo per costruire un oggetto Bar. Se la risposta è sì, allora è un dettaglio di implementazione e non dovresti preoccuparti di testare l'interazione in modo specifico (fai riferimento alla risposta di @ Michael). Se la risposta è no, allora stai modificando la classe perché la tua difficoltà nel test è una bandiera rossa che il tuo design ha bisogno di un piccolo miglioramento (quindi il bonus che ho aggiunto riguardo al modo in cui TDD guida la progettazione).
csturtz,

3
Puoi passare un oggetto "reale" alla "verifica" di Mockito?
Giovanni B,

4
Puoi anche deridere la fabbrica: BarFactory myFactory = mock(BarFactory.class); when(myFactory.createBar()).thenReturn(bar);
levsa

18

La risposta classica è "Non lo fai". Si verifica l'API pubblica di Foo, non i suoi interni.

C'è qualche comportamento Foodell'oggetto (o, meno buono, qualche altro oggetto nell'ambiente) che è interessato foo()? In tal caso, provalo. E se no, cosa fa il metodo?


4
Quindi cosa testeresti qui? L'API pubblica di Fooè public void foo(), dove gli interni sono solo correlati alla barra.
Behelit,

15
Testare solo l'API pubblica va bene, fino a quando non ci sono veri bug con effetti collaterali che richiedono test. Ad esempio, verificare che un metodo privato stia chiudendo correttamente le sue connessioni HTTP è eccessivo fino a quando non si scopre che il metodo privato non sta chiudendo correttamente le sue connessioni e quindi causa un grosso problema. A quel punto, Mockito e verify()diventerai davvero molto utile, anche se non stai più adorando al santo altare dei test di integrazione.
Dawngerpony,

@DuffJ Non uso Java, ma sembra qualcosa che il tuo compilatore o strumento di analisi del codice dovrebbe rilevare.
user247702

3
Sono d'accordo con DuffJ, mentre la programmazione funzionale è divertente, arriva un punto in cui il tuo codice interagisce con il mondo esterno. Non importa se lo chiami "interni", "effetti collaterali" o "funzionalità", vuoi sicuramente testare quell'interazione: se succede, e se succede il numero corretto di volte e con gli argomenti corretti. @Stijn: potrebbe essere stato un cattivo esempio (ma se si dovessero aprire più connessioni e solo alcune di quelle chiuse, diventa interessante). Un esempio migliore sarebbe quello di verificare il tempo in cui i dati corretti sarebbero stati inviati tramite la connessione.
Andras Balázs Lajtha,

13

Se non si desidera utilizzare DI o Factory. Puoi riformattare la tua classe in un modo un po 'complicato:

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

E la tua classe di 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();
    }
}

Quindi la classe che chiama il tuo metodo foo lo farà in questo modo:

public class thirdClass {

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

Come puoi vedere quando chiami il metodo in questo modo, non è necessario importare la classe Bar in qualsiasi altra classe che chiama il tuo metodo foo che è forse qualcosa che desideri.

Naturalmente il rovescio della medaglia è che si sta permettendo al chiamante di impostare l'oggetto barra.

Spero che sia d'aiuto.


3
Penso che questo sia un anti-pattern. Le dipendenze dovrebbero essere iniettate, punto. Consentire una dipendenza facoltativamente iniettata al solo scopo di test è evitare intenzionalmente di migliorare il codice e testare intenzionalmente qualcosa di diverso rispetto al codice che viene eseguito in produzione. Entrambi sono cose orribili e orribili da fare.
ErikE

8

Soluzione per il tuo codice di esempio utilizzando 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();

    }

}

Uscita JUnit Uscita JUnit


8

Penso che Mockito @InjectMockssia la strada da percorrere.

A seconda delle tue intenzioni puoi usare:

  1. Iniezione del costruttore
  2. Iniezione setter proprietà
  3. Iniezione di campo

Maggiori informazioni nei documenti

Di seguito è riportato un esempio con l'iniezione sul campo:

Classi:

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

3

Sì, se vuoi davvero / hai bisogno di farlo, puoi usare PowerMock. Questo dovrebbe essere considerato l'ultima risorsa. Con PowerMock puoi far sì che restituisca una simulazione dalla chiamata al costruttore. Quindi esegui la verifica sul modello. Detto questo, Csturtz è la risposta "giusta".

Ecco il link alla costruzione simulata di nuovi oggetti


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.