Verspotten Sie einen Konstruktor mit Parameter

87

Ich habe eine Klasse wie folgt:

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

    public String check() {
        bla bla bla
    }
}

Die Logik im Konstruktor A(String test)und check()sind die Dinge, die ich verspotten möchte. Ich möchte alle Aufrufe wie: new A($$$any string$$$).check()Gibt eine Dummy-Zeichenfolge zurück "test".

Ich habe es versucht:

 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

Aber es scheint nicht zu funktionieren. new A($$$any string$$$).check()durchläuft immer noch die Konstruktorlogik, anstatt das verspottete Objekt von abzurufen A.

Shengjie
quelle
funktioniert Ihre verspottete check () -Methode richtig?
Ben Glasser
@ BenGlasser check () funktioniert ok. Nur das whenNew scheint überhaupt nicht zu funktionieren. Ich habe auch die Beschreibung aktualisiert.
Shengjie

Antworten:

92

Der Code, den Sie gepostet haben, funktioniert für mich mit der neuesten Version von Mockito und Powermockito. Vielleicht hast du A nicht vorbereitet? Versuche dies:

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

Beide Tests sollten mit mockito 1.9.0, powermockito 1.4.12 und junit 4.8.2 bestanden werden

Alban
quelle
23
Beachten Sie auch, dass, wenn der Konstruktor aus einer anderen Klasse aufgerufen wird, Sie ihn in die Liste inPrepareForTest
Jeff E
Jeder hat eine Idee, warum wir das Selbst vorbereiten sollten, wenn "PowerMockito.whenNew" aufgerufen wird?
Udayanga
50

Meines Wissens können Sie Konstruktoren nicht mit Mockito verspotten, sondern nur mit Methoden. Laut dem Wiki auf der Google-Codepage von Mockito gibt es jedoch eine Möglichkeit, das Konstruktorverhalten zu verspotten, indem in Ihrer Klasse eine Methode erstellt wird, die eine neue Instanz dieser Klasse zurückgibt. dann können Sie diese Methode verspotten. Unten ist ein Auszug direkt aus dem Mockito-Wiki :

Muster 1 - Verwenden einzeiliger Methoden zur Objekterstellung

Um Muster 1 zu verwenden (Testen einer Klasse namens MyClass), würden Sie einen Aufruf wie ersetzen

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

mit

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

und schreiben Sie eine einzeilige Methode

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

Es ist wichtig, dass Sie keine Logik in die Methode aufnehmen. Nur die eine Zeile, die das Objekt erstellt. Der Grund dafür ist, dass die Methode selbst niemals einem Unit-Test unterzogen wird.

Wenn Sie zum Testen der Klasse kommen, ist das Objekt, das Sie testen, tatsächlich ein Mockito-Spion, bei dem diese Methode überschrieben wird, um einen Schein zurückzugeben. Was Sie testen, ist daher nicht die Klasse selbst, sondern eine sehr leicht modifizierte Version davon.

Ihre Testklasse enthält möglicherweise Mitglieder wie

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

Zuletzt verspotten Sie in Ihrer Testmethode den Aufruf von makeFoo mit einer Zeile wie

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

Sie können Matcher verwenden, die spezifischer sind als any (), wenn Sie die Argumente überprüfen möchten, die an den Konstruktor übergeben werden.

Wenn Sie nur ein verspottetes Objekt Ihrer Klasse zurückgeben möchten, sollte dies für Sie funktionieren. In jedem Fall können Sie hier mehr über das Verspotten der Objekterstellung lesen:

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

Ben Glasser
quelle
20
+1, ich mag die Tatsache nicht, dass ich meinen Quellcode anpassen muss, um ihn mockito-freundlicher zu machen. Danke für das Teilen.
Shengjie
21
Es ist nie schlecht, Quellcode zu haben, der besser testbar ist, oder Testmuster-Anti-Patterns zu vermeiden, wenn Sie Ihren Code schreiben. Wenn Sie eine Quelle schreiben, die besser testbar ist, ist sie automatisch besser zu warten. Das Isolieren Ihrer Konstruktoraufrufe in ihren eigenen Methoden ist nur eine Möglichkeit, dies zu erreichen.
Dawood ibn Kareem
Das Schreiben von testbarem Code ist gut. Es fühlt sich ... weniger gut an, gezwungen zu sein, Klasse A neu zu gestalten, damit ich Tests für Klasse B schreiben kann, die von A abhängt, weil A eine fest codierte Abhängigkeit von C hat. Ja, der Code wird am Ende besser sein, aber wie viele Klassen werde ich am Ende neu gestalten, damit ich einen Test fertig schreiben kann?
Mark Wood
@MarkWood Nach meiner Erfahrung sind klobige Testerfahrungen im Allgemeinen ein Zeichen für einen Designfehler. IRL Wenn Sie Konstruktoren testen, schreit Ihr Code Sie wahrscheinlich nach einer Factory oder einer Abhängigkeitsinjektion an. Wenn Sie den typischen Entwurfsmustern für diese beiden Fälle folgen, ist Ihr Code im Allgemeinen viel einfacher zu testen und zu bearbeiten. Wenn Sie Konstruktoren testen, weil Sie eine Menge Logik darin haben, benötigen Sie wahrscheinlich eine Schicht Polymorphismus, oder Sie könnten diese Logik auf eine Initialisierungsmethode verschieben.
Ben Glasser
12

Ohne Powermock zu verwenden ... Siehe das folgende Beispiel basierend auf der Antwort von Ben Glasser, da ich einige Zeit gebraucht habe, um es herauszufinden. Hoffentlich spart das einige Male ...

Ursprüngliche Klasse:

public class AClazz {

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

Geänderte Klasse:

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

Testklasse

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);
    }
}
user666
quelle
4

Mockito hat Einschränkungen beim Testen endgültiger, statischer und privater Methoden.

Mit der jMockit-Testbibliothek können Sie einige Dinge wie folgt sehr einfach und unkompliziert erledigen:

Mock-Konstruktor einer java.io.File-Klasse:

new MockUp<File>(){
    @Mock
    public void $init(String pathname){
        System.out.println(pathname);
        // or do whatever you want
    }
};
  • Der Name des öffentlichen Konstruktors sollte durch $ init ersetzt werden
  • Argumente und Ausnahmen bleiben gleich
  • Der Rückgabetyp sollte als ungültig definiert werden

Verspotten Sie eine statische Methode:

  • Entfernen Sie statische Daten aus der Methoden-Mock-Signatur
  • Die Methodensignatur bleibt ansonsten gleich
Amit Kaneria
quelle
4

Mit mockito können Sie withSettings () verwenden. Wenn der CounterService beispielsweise zwei Abhängigkeiten benötigt, können Sie diese als mock übergeben:

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

MevlütÖzdemir
quelle
Meiner Meinung nach die einfachste und beste Antwort. Danke dir.
Eldon