Mock un costruttore con parametro


90

Ho una classe come di seguito:

public class A {
    public A(String test) {
        bla bla bla
    }

    public String check() {
        bla bla bla
    }
}

La logica nel costruttore A(String test)e check()sono le cose che sto cercando di deridere. Voglio chiamate come: new A($$$any string$$$).check()restituisce una stringa fittizia "test".

Provai:

 A a = mock(A.class); 
 when(a.check()).thenReturn("test");

 String test = a.check(); // to this point, everything works. test shows as "tests"

 whenNew(A.class).withArguments(Matchers.anyString()).thenReturn(rk);
 // also tried:
 //whenNew(A.class).withParameterTypes(String.class).withArguments(Matchers.anyString()).thenReturn(rk);

 new A("random string").check();  // this doesn't work

Ma non sembra funzionare. new A($$$any string$$$).check()sta ancora attraversando la logica del costruttore invece di recuperare l'oggetto deriso di A.


il tuo metodo beffato check () funziona bene?
Ben Glasser

@BenGlasser check () funziona bene. Proprio il whenNew sembra non funzionare affatto. Ho aggiornato anche la descrizione.
Shengjie

Risposte:


93

Il codice che hai pubblicato funziona per me con l'ultima versione di Mockito e Powermockito. Forse non hai preparato A? Prova questo:

A.java

public class A {
     private final String test;

    public A(String test) {
        this.test = test;
    }

    public String check() {
        return "checked " + this.test;
    }
}

MockA.java

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

@RunWith(PowerMockRunner.class)
@PrepareForTest(A.class)
public class MockA {
    @Test
    public void test_not_mocked() throws Throwable {
        assertThat(new A("random string").check(), equalTo("checked random string"));
    }
    @Test
    public void test_mocked() throws Throwable {
         A a = mock(A.class); 
         when(a.check()).thenReturn("test");
         PowerMockito.whenNew(A.class).withArguments(Mockito.anyString()).thenReturn(a);
         assertThat(new A("random string").check(), equalTo("test"));
    }
}

Entrambi i test dovrebbero passare con mockito 1.9.0, powermockito 1.4.12 e junit 4.8.2


25
Si noti inoltre che se il costruttore viene chiamato da un'altra classe, includerlo nell'elenco inPrepareForTest
Jeff E

Qualcuno ha un'idea del perché dovremmo preparare il sé quando viene chiamato "PowerMockito.whenNew"?
udayanga

50

Per quanto ne so, non puoi deridere i costruttori con mockito, solo metodi. Ma secondo il wiki sulla code page di Google Mockito, c'è un modo per deridere il comportamento del costruttore creando un metodo nella tua classe che restituisce una nuova istanza di quella classe. allora puoi deridere quel metodo. Di seguito è riportato un estratto direttamente dal wiki di Mockito :

Pattern 1: utilizzo di metodi su una riga per la creazione di oggetti

Per usare il pattern 1 (testare una classe chiamata MyClass), dovresti sostituire una chiamata come

   Foo foo = new Foo( a, b, c );

con

   Foo foo = makeFoo( a, b, c );

e scrivi un metodo di una riga

   Foo makeFoo( A a, B b, C c ) { 
        return new Foo( a, b, c );
   }

È importante non includere alcuna logica nel metodo; solo l'unica linea che crea l'oggetto. La ragione di ciò è che il metodo stesso non verrà mai testato in unità.

Quando verrai a testare la classe, l'oggetto che testerai sarà effettivamente una spia Mockito, con questo metodo ignorato, per restituire una simulazione. Quello che stai testando quindi non è la classe stessa, ma una versione leggermente modificata di essa.

La tua classe di test potrebbe contenere membri come

  @Mock private Foo mockFoo;
  private MyClass toTest = spy(new MyClass());

Infine, all'interno del tuo metodo di test prendi in giro la chiamata a makeFoo con una linea come

  doReturn( mockFoo )
      .when( toTest )
      .makeFoo( any( A.class ), any( B.class ), any( C.class ));

È possibile utilizzare abbinamenti più specifici di any () se si desidera controllare gli argomenti passati al costruttore.

Se vuoi solo restituire un oggetto deriso della tua classe, penso che dovrebbe funzionare per te. In ogni caso puoi leggere di più sulla creazione di oggetti mocking qui:

http://code.google.com/p/mockito/wiki/MockingObjectCreation


21
+1, non mi piace il fatto di dover modificare il mio codice sorgente per renderlo più adatto ai mockito. Grazie per la condivisione.
Shengjie

23
Non è mai male avere un codice sorgente più testabile o evitare anti-pattern di testabilità quando scrivi il tuo codice. Se scrivi una sorgente che è più testabile, è automaticamente più gestibile. Isolare le chiamate del costruttore nei propri metodi è solo un modo per ottenere ciò.
Dawood ibn Kareem

1
Scrivere codice verificabile è buono. Essere costretto a riprogettare la classe A in modo da poter scrivere test per la classe B, che dipende da A, perché A ha una dipendenza hardcoded da C, mi sembra ... meno buono. Sì, il codice alla fine sarà migliore, ma quante classi finirò per riprogettare in modo da poter finire di scrivere un test?
Mark Wood,

@MarkWood nella mia esperienza esperienze di test goffi sono generalmente un segno di qualche difetto di progettazione. IRL se stai testando i costruttori, il tuo codice probabilmente ti sta urlando contro per una factory o un'iniezione di dipendenza. Se segui i modelli di progettazione tipici per questi due casi, il tuo codice diventa molto più facile da testare e da utilizzare in generale. Se stai testando costruttori perché hai un sacco di logica lì dentro, probabilmente hai bisogno di uno strato di polimorfismo, oppure potresti spostare quella logica in un metodo di inizializzazione.
Ben Glasser

12

Senza usare Powermock .... Guarda l'esempio qui sotto basato sulla risposta di Ben Glasser poiché mi ci è voluto del tempo per capirlo .. spero che ti risparmi alcune volte ...

Classe originale:

public class AClazz {

    public void updateObject(CClazz cClazzObj) {
        log.debug("Bundler set.");
        cClazzObj.setBundler(new BClazz(cClazzObj, 10));
    } 
}

Classe modificata:

@Slf4j
public class AClazz {

    public void updateObject(CClazz cClazzObj) {
        log.debug("Bundler set.");
        cClazzObj.setBundler(getBObject(cClazzObj, 10));
    }

    protected BClazz getBObject(CClazz cClazzObj, int i) {
        return new BClazz(cClazzObj, 10);
    }
 }

Classe di prova

public class AClazzTest {

    @InjectMocks
    @Spy
    private AClazz aClazzObj;

    @Mock
    private CClazz cClazzObj;

    @Mock
    private BClazz bClassObj;

    @Before
    public void setUp() throws Exception {
        Mockito.doReturn(bClassObj)
               .when(aClazzObj)
               .getBObject(Mockito.eq(cClazzObj), Mockito.anyInt());
    }

    @Test
    public void testConfigStrategy() {
        aClazzObj.updateObject(cClazzObj);

        Mockito.verify(cClazzObj, Mockito.times(1)).setBundler(bClassObj);
    }
}

7

Con mockito puoi usare withSettings (), ad esempio se CounterService richiede 2 dipendenze, puoi passarle come mock:

UserService userService = Mockito.mock(UserService.class); SearchService searchService = Mockito.mock(SearchService.class); CounterService counterService = Mockito.mock(CounterService.class, withSettings().useConstructor(userService, searchService));


A mio parere, la risposta più semplice e migliore. Grazie.

4

Mockito ha limitazioni che testano i metodi finali, statici e privati.

con la libreria di test jMockit, puoi fare poche cose in modo molto semplice e diretto come di seguito:

Mock costruttore di una classe java.io.File:

new MockUp<File>(){
    @Mock
    public void $init(String pathname){
        System.out.println(pathname);
        // or do whatever you want
    }
};
  • il nome del costruttore pubblico dovrebbe essere sostituito con $ init
  • gli argomenti e le eccezioni lanciati rimangono gli stessi
  • il tipo restituito dovrebbe essere definito come void

Mock un metodo statico:

  • rimuove static dalla firma fittizia del metodo
  • altrimenti la firma del metodo rimane la stessa
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.