Inizializzazione di oggetti fittizi - MockIto


122

Esistono molti modi per inizializzare un oggetto fittizio utilizzando MockIto. Qual è il modo migliore tra questi?

1.

 public class SampleBaseTestCase {

   @Before public void initMocks() {
       MockitoAnnotations.initMocks(this);
   }

2.

@RunWith(MockitoJUnitRunner.class)

[EDIT] 3.

mock(XXX.class);

suggeriscimi se ci sono altri modi migliori di questi ...

Risposte:


153

Per l'inizializzazione mock , utilizzando il runner o MockitoAnnotations.initMockssono soluzioni strettamente equivalenti. Dal javadoc di MockitoJUnitRunner :

JUnit 4.5 runner initializes mocks annotated with Mock, so that explicit usage of MockitoAnnotations.initMocks(Object) is not necessary. Mocks are initialized before each test method.


La prima soluzione (con il MockitoAnnotations.initMocks) potrebbe essere utilizzata quando hai già configurato un runner specifico ( SpringJUnit4ClassRunnerad esempio) sul tuo test case.

La seconda soluzione (con il MockitoJUnitRunner) è la più classica e la mia preferita. Il codice è più semplice. L'utilizzo di un runner offre il grande vantaggio della convalida automatica dell'utilizzo del framework (descritto da @David Wallace in questa risposta ).

Entrambe le soluzioni consentono di condividere i mock (e le spie) tra i metodi di prova. Insieme a @InjectMocks, consentono di scrivere test unitari molto rapidamente. Il codice di mocking boilerplate è ridotto, i test sono più facili da leggere. Per esempio:

@RunWith(MockitoJUnitRunner.class)
public class ArticleManagerTest {

    @Mock private ArticleCalculator calculator;
    @Mock(name = "database") private ArticleDatabase dbMock;
    @Spy private UserProvider userProvider = new ConsumerUserProvider();

    @InjectMocks private ArticleManager manager;

    @Test public void shouldDoSomething() {
        manager.initiateArticle();
        verify(database).addListener(any(ArticleListener.class));
    }

    @Test public void shouldDoSomethingElse() {
        manager.finishArticle();
        verify(database).removeListener(any(ArticleListener.class));
    }
}

Pro: il codice è minimo

Contro: Black magic. IMO è principalmente dovuto all'annotazione @InjectMocks. Con questa annotazione " perdi il dolore del codice" (vedi gli ottimi commenti di @Brice )


La terza soluzione è creare il tuo mock su ogni metodo di test. Permette come spiegato da @mlk nella sua risposta di avere un " test autonomo ".

public class ArticleManagerTest {

    @Test public void shouldDoSomething() {
        // given
        ArticleCalculator calculator = mock(ArticleCalculator.class);
        ArticleDatabase database = mock(ArticleDatabase.class);
        UserProvider userProvider = spy(new ConsumerUserProvider());
        ArticleManager manager = new ArticleManager(calculator, 
                                                    userProvider, 
                                                    database);

        // when 
        manager.initiateArticle();

        // then 
        verify(database).addListener(any(ArticleListener.class));
    }

    @Test public void shouldDoSomethingElse() {
        // given
        ArticleCalculator calculator = mock(ArticleCalculator.class);
        ArticleDatabase database = mock(ArticleDatabase.class);
        UserProvider userProvider = spy(new ConsumerUserProvider());
        ArticleManager manager = new ArticleManager(calculator, 
                                                    userProvider, 
                                                    database);

        // when 
        manager.finishArticle();

        // then
        verify(database).removeListener(any(ArticleListener.class));
    }
}

Pro: dimostri chiaramente come funziona la tua API (BDD ...)

Contro: c'è più codice boilerplate. (La creazione beffa)


La mia raccomandazione è un compromesso. Utilizza l' @Mockannotazione con @RunWith(MockitoJUnitRunner.class), ma non utilizzare @InjectMocks:

@RunWith(MockitoJUnitRunner.class)
public class ArticleManagerTest {

    @Mock private ArticleCalculator calculator;
    @Mock private ArticleDatabase database;
    @Spy private UserProvider userProvider = new ConsumerUserProvider();

    @Test public void shouldDoSomething() {
        // given
        ArticleManager manager = new ArticleManager(calculator, 
                                                    userProvider, 
                                                    database);

        // when 
        manager.initiateArticle();

        // then 
        verify(database).addListener(any(ArticleListener.class));
    }

    @Test public void shouldDoSomethingElse() {
        // given
        ArticleManager manager = new ArticleManager(calculator, 
                                                    userProvider, 
                                                    database);

        // when 
        manager.finishArticle();

        // then 
        verify(database).removeListener(any(ArticleListener.class));
    }
}

Pro: dimostri chiaramente come funziona la tua API (come ArticleManagerviene istanziata la mia ). Nessun codice boilerplate.

Contro: il test non è autonomo, meno problemi di codice


Fai attenzione però, le annotazioni sono utili ma non ti proteggono dalla creazione di un design OO scadente (o dal degrado). Personalmente, mentre sono felice di ridurre il codice boilerplate, perdo il dolore del codice (o PITA) che è il fattore scatenante per cambiare il design in uno migliore, quindi io e il team stiamo prestando attenzione al design OO. Sento che seguire il design OO con principi come il design SOLID o le idee GOOS sia molto più importante che scegliere come istanziare i mock.
Brice

1
(follow-up) Se non vedi come viene creato questo oggetto non ne senti il ​​dolore e i futuri programmatori potrebbero non reagire bene se fosse necessario aggiungere nuove funzionalità. Comunque questo è discutibile in entrambi i modi, sto solo dicendo di stare attento a questo proposito.
Brice

6
NON È CORRETTO che questi due siano equivalenti. NON È VERO che il codice più semplice sia l'unico vantaggio nell'utilizzo MockitoJUnitRunner. Per ulteriori informazioni sulle differenze, vedere la domanda su stackoverflow.com/questions/10806345/… e la mia risposta.
Dawood ibn Kareem

2
@Gontard Sì, certo che le dipendenze sono visibili, ma ho visto che il codice è andato storto usando questo approccio. Sull'utilizzo del Collaborator collab = mock(Collaborator.class), a mio avviso in questo modo è sicuramente un approccio valido. Sebbene questo possa tendere ad essere prolisso, puoi guadagnare in comprensibilità e refactoring dei test. Entrambi i modi hanno i loro pro e contro, non ho ancora deciso quale approccio sia migliore. Amyway è sempre possibile scrivere schifezze, e probabilmente dipende dal contesto e dal programmatore.
Brice

1
@mlk sono totalmente d'accordo con te. Il mio inglese non è molto buono e manca di sfumature. Il mio punto era insistere sulla parola UNIT.
gontard

30

Esiste ora (a partire dalla v1.10.7) un quarto modo per istanziare i mock, che utilizza una regola JUnit4 chiamata MockitoRule .

@RunWith(JUnit4.class)   // or a different runner of your choice
public class YourTest
  @Rule public MockitoRule rule = MockitoJUnit.rule();
  @Mock public YourMock yourMock;

  @Test public void yourTestMethod() { /* ... */ }
}

JUnit cerca le sottoclassi di TestRule annotate con @Rule e le utilizza per racchiudere le dichiarazioni di prova fornite dal Runner . Il risultato è che puoi estrarre metodi @Before, metodi @After e persino provare ... a catturare i wrapper nelle regole. Puoi persino interagire con questi dall'interno del tuo test, nel modo in cui fa ExpectedException .

MockitoRule si comporta quasi esattamente come MockitoJUnitRunner , tranne per il fatto che puoi usare qualsiasi altro runner, come Parameterized (che consente ai tuoi costruttori di test di prendere argomenti in modo che i tuoi test possano essere eseguiti più volte), o il test runner di Robolectric (quindi il suo classloader può fornire sostituzioni Java per classi native Android). Questo lo rende strettamente più flessibile da usare nelle recenti versioni di JUnit e Mockito.

In sintesi:

  • Mockito.mock(): Invocazione diretta senza supporto per annotazioni o convalida dell'utilizzo.
  • MockitoAnnotations.initMocks(this): Supporto per annotazioni, nessuna convalida dell'utilizzo.
  • MockitoJUnitRunner: Supporto delle annotazioni e convalida dell'utilizzo, ma è necessario utilizzare quel corridore.
  • MockitoRule: Supporto delle annotazioni e convalida dell'utilizzo con qualsiasi runner JUnit.

Vedi anche: Come funziona JUnit @Rule?


3
A Kotlin, la regola è così:@get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
Cristan

10

C'è un modo pulito per farlo.

  • Se è uno Unit Test puoi farlo:

    @RunWith(MockitoJUnitRunner.class)
    public class MyUnitTest {
    
        @Mock
        private MyFirstMock myFirstMock;
    
        @Mock
        private MySecondMock mySecondMock;
    
        @Spy
        private MySpiedClass mySpiedClass = new MySpiedClass();
    
        // It's gonna inject the 2 mocks and the spied object per reflection to this object
        // The java doc of @InjectMocks explains it really well how and when it does the injection
        @InjectMocks
        private MyClassToTest myClassToTest;
    
        @Test
        public void testSomething() {
        }
    }
  • EDIT: Se si tratta di un test di integrazione, puoi farlo (non inteso per essere utilizzato in questo modo con Spring. Mostra solo che puoi inizializzare i mock con diversi Runner):

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration("aplicationContext.xml")
    public class MyIntegrationTest {
    
        @Mock
        private MyFirstMock myFirstMock;
    
        @Mock
        private MySecondMock mySecondMock;
    
        @Spy
        private MySpiedClass mySpiedClass = new MySpiedClass();
    
        // It's gonna inject the 2 mocks and the spied object per reflection to this object
        // The java doc of @InjectMocks explains it really well how and when it does the injection
        @InjectMocks
        private MyClassToTest myClassToTest;
    
        @Before
        public void setUp() throws Exception {
              MockitoAnnotations.initMocks(this);
        }
    
        @Test
        public void testSomething() {
        }
    }

1
Se MOCK è coinvolto anche nei test di integrazione, avrà senso?
VinayVeluri

2
in realtà non lo farà, hai ragione. Volevo solo mostrare le possibilità di Mockito. Ad esempio, se usi RESTFuse devi usare il loro runner in modo da poter inizializzare i mock con MockitoAnnotations.initMocks (this);
emd

8

Le annotazioni e il corridore sono stati ben discussi sopra, quindi ho intenzione di gettare il mio tuppence per i non amati:

XXX mockedXxx = mock(XXX.class);

Lo uso perché lo trovo un po 'più descrittivo e preferisco (non banalmente escluso) i test unitari non utilizzare le variabili membro poiché mi piace che i miei test siano (per quanto possono essere) autonomi.


C'è qualche altro vantaggio rispetto all'uso di mock (XX.class) eccetto che il test case sia autonomo?
VinayVeluri

Non per quanto ne so.
Michael Lloyd Lee mlk

3
Meno magia da capire per leggere il test. Dichiari la variabile e le dai un valore - nessuna annotazione, riflessione ecc.
Karu

2

Un piccolo esempio per JUnit 5 Jupiter, "RunWith" è stato rimosso, ora è necessario utilizzare le estensioni utilizzando l'annotazione "@ExtendWith".

@ExtendWith(MockitoExtension.class)
class FooTest {

  @InjectMocks
  ClassUnderTest test = new ClassUnderTest();

  @Spy
  SomeInject bla = new SomeInject();
}

0

Le altre risposte sono ottime e contengono maggiori dettagli se le desideri / ne hai bisogno.
Oltre a questi, vorrei aggiungere un TL; DR:

  1. Preferisco usare
    • @RunWith(MockitoJUnitRunner.class)
  2. Se non puoi (perché usi già un corridore diverso), preferisci usare
    • @Rule public MockitoRule rule = MockitoJUnit.rule();
  3. Simile a (2), ma non dovresti più usarlo:
    • @Before public void initMocks() { MockitoAnnotations.initMocks(this); }
  4. Se vuoi usare un mock in uno solo dei test e non vuoi esporlo ad altri test nella stessa classe di test, usa
    • X x = mock(X.class)

(1) e (2) e (3) si escludono a vicenda.
(4) può essere utilizzato in combinazione con gli altri.

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.