2013-03-19 14 views
69

Esistono molti modi per inizializzare un oggetto fittizio usando MockIto. Qual è il modo migliore tra questi?Inizializzazione di oggetti fittizi - MockIto

1.

public class SampleBaseTestCase { 

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

2.

@RunWith(MockitoJUnitRunner.class) 

[EDIT] 3.

mock(XXX.class); 

suggeriscono me se ci sono altri modi migliori di questi ...

risposta

87

Per l'inizializzazione dei banchi, l'utilizzo del corridore o dello MockitoAnnotations.initMocks sono soluzioni strettamente equivalenti. Dal javadoc del 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 la MockitoAnnotations.initMocks) potrebbe essere utilizzato quando si è già configurato un corridore specifico (SpringJUnit4ClassRunner per esempio) sul banco di prova.

La seconda soluzione (con il MockitoJUnitRunner) è la più classica e la mia preferita. Il codice è più semplice. L'utilizzo di un corridore offre il grande vantaggio di automatic validation of framework usage (descritto da @David Wallace in this answer).

Entrambe le soluzioni consentono di condividere i mock (e spie) tra i metodi di test. Abbinati allo @InjectMocks, consentono di scrivere test di unità molto rapidamente. Il codice mocking del 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 è minima

Contro: magia nera. IMO è dovuto principalmente all'annotazione @InjectMocks. Con questa annotazione "si perde il dolore di codice" (vedere le grandi commenti di @Brice)


La terza soluzione è quella di creare la vostra finta su ogni metodo di prova. Permette come spiegato da @mlk nella sua risposta di avere "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: È dimostrano chiaramente come funziona il vostro API (BDD) ...

Contro: non v'è più codice boilerplate. (La creazione prende in giro)


mio sicuramente al livello è un compromesso.Utilizzare il @Mock annotazioni con il @RunWith(MockitoJUnitRunner.class), ma non utilizzare i @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: È dimostrano chiaramente come funziona il vostro API (come il mio ArticleManager viene istanziato). Nessun codice standard.

Contro: Il test non è autonomo, meno dolore di codice

+0

Attenzione però, le annotazioni sono utili, ma non proteggono contro le imbarcazioni da cattiva progettazione OO (o degradarlo). Personalmente, mentre sono felice di ridurre il codice boilerplate, perdo il dolore del codice (o PITA) che è il grilletto per cambiare il design in uno migliore, così io e il team prestiamo attenzione al design OO. Sento che seguire il design OO con principi come il design SOLID o le idee GOOS è molto più importante che scegliere come istanziare i mock. – Brice

+0

@Brice perché? se la classe sotto test ('ArticleManager') ha troppe dipendenze, la vedrò chiaramente. E questo è fuori tema, la domanda è solo "come istanziare i mock?" – gontard

+0

Avrei dovuto dire "meno ovvio" :) Quindi in realtà @injectMocks proverà il meglio per iniettare i mock automaticamente senza che l'utente debba collegare le cose (come siamo abituati a fare in primavera o in guisa), quindi l'istanza dell'oggetto è nascosta, non si sa se è l'iniezione del costruttore o l'iniezione setter, che potrebbe essere fastidioso per l'uso futuro di questo oggetto (la riusabilità è uno dei principali vantaggi del buon design OO). – Brice

9

C'è un modo pulito di fare questo.

  • Se si tratta di un test di unità si può fare questo:

    @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 si può fare questo (non destinati ad essere utilizzati in questo modo con la Primavera Basta mostrare che è possibile inizializzare prende in giro con i corridori diferent):

    @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 anche MOCK è coinvolto nei test di integrazione, ha senso? – VinayVeluri

+1

in realtà non lo farà, il tuo diritto. 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 (questo); – emd

5

MockitoAnnotations & la corridore sono stati ben discusso in precedenza, quindi ho intenzione di gettare la mia tuppence per i non amati:

XXX mockedXxx = mock(XXX.class); 

Io uso questo, perché lo trovo un po 'più descrittivo e preferisco (non divieto di uscire a destra) Unità prova a non usare variabili membro come mi piace che i miei test siano (per quanto possano essere) autonomi.

+0

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

+0

Non per quanto ne so. –

+2

Meno magia da capire per poter leggere il test. Dichiarate la variabile e assegnatene un valore, senza annotazioni, riflessioni, ecc. – Karu

17

c'è ora (come di v1.10.7) un quarto modo per istanziare prende in giro, che sta utilizzando un JUnit4 regola chiamato 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 subclasses of TestRule annotated with @Rule, e li utilizza per avvolgere le dichiarazioni di prova che il corridore fornisce. Il risultato è che puoi estrarre i metodi @Before, @After e persino provare ... catturare i wrapper in regole. Puoi persino interagire con questi dal tuo test, come fa ExpectedException.

MockitoRule si comporta quasi esattamente come MockitoJUnitRunner, tranne che è possibile utilizzare qualsiasi altro corridore, come ad esempio Parameterized (che consente ai costruttori di prova per prendere argomenti in modo che i test possono essere eseguiti più volte), o test runner di Robolectric (così il suo classloader può fornire sostituzioni Java per le classi native Android). Questo lo rende molto più flessibile da utilizzare nelle recenti versioni di JUnit e Mockito.

In sintesi:

  • Mockito.mock(): invocazione diretto senza supporto annotazione o la convalida di utilizzo.
  • MockitoAnnotations.initMocks(this): supporto annotazione, nessuna convalida dell'utilizzo.
  • MockitoJUnitRunner: supporto annotazione e convalida dell'utilizzo, ma è necessario utilizzare quel corridore.
  • MockitoRule: supporto annotazione e convalida dell'utilizzo con qualsiasi runner JUnit.

Consulta anche: How JUnit @Rule works?

Problemi correlati