Mehrere RunWith-Anweisungen in jUnit

113

Ich schreibe Unit Test und möchte JUnitParamsRunnerund MockitoJUnitRunnerfür eine Testklasse verwenden.

Leider funktioniert folgendes nicht:

@RunWith(MockitoJUnitRunner.class)
@RunWith(JUnitParamsRunner.class)
public class DatabaseModelTest {
  // some tests
}

Gibt es eine Möglichkeit, Mockito und JUnitParams in einer Testklasse zu verwenden?

Hans-Helge
quelle
2
Schauen Sie mal rein
Alexey Gavrilov
2
Es gibt auch hier ein schönes Beispiel: blog.project13.pl/index.php/coding/1077/…
falsarella

Antworten:

110

Sie können dies nicht tun, da Sie gemäß Spezifikation dieselbe Annotation nicht zweimal auf dasselbe annotierte Element setzen können.

Also, was ist die Lösung? Die Lösung besteht darin, nur einen @RunWith()mit einem Läufer zu platzieren, ohne den Sie nicht stehen können, und den anderen durch einen anderen zu ersetzen. In Ihrem Fall werden Sie wahrscheinlich entfernen MockitoJUnitRunnerund programmgesteuert tun, was es tut.

Tatsächlich läuft es nur so:

MockitoAnnotations.initMocks(test);

am Anfang des Testfalls. Die einfachste Lösung besteht also darin, diesen Code in eine setUp()Methode einzufügen:

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

Ich bin nicht sicher, aber wahrscheinlich sollten Sie einen Mehrfachaufruf dieser Methode mit flag vermeiden:

private boolean mockInitialized = false;
@Before
public void setUp() {
    if (!mockInitialized) {
        MockitoAnnotations.initMocks(this);
        mockInitialized = true;  
    }
}

Eine bessere, wiederverwendbare Lösung kann jedoch mit den Regeln von JUnt implementiert werden.

public class MockitoRule extends TestWatcher {
    private boolean mockInitialized = false;

    @Override
    protected void starting(Description d) {
        if (!mockInitialized) {
            MockitoAnnotations.initMocks(this);
            mockInitialized = true;  
        }
    }
}

Fügen Sie Ihrer Testklasse nun einfach die folgende Zeile hinzu:

@Rule public MockitoRule mockitoRule = MockitoJUnit.rule();

und Sie können diesen Testfall mit jedem gewünschten Läufer ausführen.

AlexR
quelle
12
Die Prüfung auf mockInitializedist falsch. Sie möchten für jeden Tetst einen neuen Mock haben.
BetaRide
1
@BetaRide, es hängt von Ihren Bedürfnissen ab. Manchmal möchten Sie jedes Mal Mock initialisieren, manchmal nicht.
AlexR
Wenn Sie einmal pro Klassendatei einrichten möchten, können Sie BeforeClass anstelle von Before verwenden, das einmal und nur einmal pro Testdatei aufgerufen wird.
InfernalRapture
56

Ab JUnit 4.7 und Mockito 1.10.17 ist diese Funktionalität integriert. Es gibt eine org.mockito.junit.MockitoRuleKlasse. Sie können es einfach importieren und die Zeile hinzufügen

@Rule public MockitoRule mockitoRule = MockitoJUnit.rule();

zu Ihrer Testklasse.

Erica Kane
quelle
2
Für ältere Versionen von Mockito (bis auf 1.10.5 scheint es) müssen Sie verwenden:@Rule public MockitoJUnitRule mockito = new MockitoJUnitRule(this);
Cliff Sun
MockitoAnnotations.initMocks(this)ist sehr langsam, um Mocks zu erstellen. Der effizienteste Weg ist die Verwendung der @Runwith (MockitoJunitRunner.class)
ant2009
16

Diese Lösung funktioniert für jeden möglichen Läufer, nicht nur für dieses Mockito-Beispiel. Beispielsweise; Ändern Sie für Spring einfach die Runner-Klassen und fügen Sie die erforderlichen Anmerkungen hinzu.

@RunWith(JUnitParamsRunner.class)
public class DatabaseModelTest {

    @Test
    public void subRunner() throws Exception {
        JUnitCore.runClasses(TestMockitoJUnitRunner.class);
    }

    @RunWith(MockitoJUnitRunner.class)
    public static class TestMockitoJUnitRunner {
    }
}

DatabaseModelTestwird von JUnit ausgeführt. TestMockitoJUnitRunnerdavon abhängt (durch die Logik) , und es wird ausgeführt werden , im Innern des Hauptes in einem @TestVerfahren, während des Anrufs JUnitCore.runClasses(TestMockitoJUnitRunner.class). Diese Methode stellt sicher, dass der Hauptläufer korrekt gestartet wird, bevor der static class TestMockitoJUnitRunnerUnterläufer ausgeführt wird, und implementiert effektiv mehrere verschachtelte @RunWithAnmerkungen mit abhängigen Testklassen.

Auch auf https://bekce.github.io/junit-multiple-runwith-dependent-tests

bekce
quelle
3
Wenn Sie anrufen, JUnitCore.runClasses()ohne das Ergebnis zu überprüfen, riskieren Sie, die Fehler aus dem inneren Test zu maskieren. assert(JUnitCore.runClasses(TestMockitoJUnitRunner.class).wasSuccessful());wird Ihnen zumindest den Fehler melden
Robotnik
2

In meinem Fall habe ich versucht, eine Methode in Spring Bean und zu verspotten

MockitoAnnotations.initMocks(test);

funktioniert nicht Stattdessen müssen Sie diese Bean so definieren, dass sie mithilfe der Mock-Methode in Ihrer XML-Datei wie folgt erstellt wird.

...
<bean id="classWantedToBeMocked" class="org.mockito.Mockito" factory-method="mock">
    <constructor-arg value="com.fullpath.ClassWantedToBeMocked" />
</bean>
...

und fügen Sie diese Bean mit Autowired in Ihrer Testklasse wie folgt hinzu.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="file:springconfig.xml")
public class TestClass {
    ...
    @Autowired
    private ClassWantedToBeMocked classWantedToBeMocked;
    ...
    when(classWantedToBeMocked.methodWantedToBeMocked()).thenReturn(...);
    ...
}
Heungwoo
quelle
0

Schauen Sie sich diesen Link an: https://bekce.github.io/junit-multiple-runwith-dependent-tests/ Mit diesem Ansatz kombinierte ich einen @ RunWith (Parameterized.class) - äußeren Läufer - mit @RunWith (MockitoJUnitRunner.class) - Innenläufer. Die einzige Änderung, die ich hinzufügen musste, bestand darin, meine Mitgliedsvariablen in der äußeren Klasse / dem Läufer statisch zu machen, um sie für den inneren / verschachtelten Läufer / die Klasse zugänglich zu machen. Glück haben und genießen.

Legna
quelle
0

Ich wollte SWTBotJunit4ClassRunner und org.junit.runners.Parameterized gleichzeitig ausführen , ich habe parametrische Tests und ich möchte Screenshots machen, wenn der SWT-Test fehlschlägt (die Screenshot-Funktion wird von bereitgestellt SWTBotJunit4ClassRunner bereitgestellt ). Die Antwort von @ bekce ist großartig und wollte zuerst diesen Weg gehen, aber es war entweder skurril, die Argumente durchzugehen. Oder führen Sie die Parameter in der Unterklasse aus und verlieren Sie die Informationen darüber, welche genauen Tests bestanden / fehlgeschlagen sind und nur den letzten Screenshot haben (da die Screenshotnamen den Namen vom Test selbst erhalten). So oder so war es ein bisschen chaotisch.

In meinem Fall ist der SWTBotJunit4ClassRunner einfach genug, also habe ich den Quellcode der Klasse geklont, ihm meinen eigenen Namen ParametrizedScreenshotRunner gegeben und wo original den TestRunner erweitert hat , erweitert meine Klasse den parametrisierte Klasse, sodass ich im Wesentlichen meinen eigenen Runner verwenden kann anstelle der beiden vorherigen. Mein eigener Läufer erstreckt sich über den parametrisierten Läufer, während die Screenshot-Funktion darüber implementiert wird. Jetzt verwendet mein Test diesen "Hybrid" -Läufer und alle Tests funktionieren wie erwartet sofort (es ist nicht erforderlich, irgendetwas innerhalb der Tests zu ändern).

So sieht es aus (der Kürze halber habe ich alle Kommentare aus der Liste entfernt):

package mySwtTests;

import org.junit.runners.Parameterized;
import org.eclipse.swtbot.swt.finder.junit.ScreenshotCaptureListener;
import org.junit.runner.notification.RunListener;
import org.junit.runner.notification.RunNotifier;

public class ParametrizedScreenshotRunner extends TestRu Parameterized {

    public ParametrizedScreenshotRunner(Class<?> klass) throws Throwable {
        super(klass);
    }

    public void run(RunNotifier notifier) {
        RunListener failureSpy = new ScreenshotCaptureListener();
        notifier.removeListener(failureSpy); // remove existing listeners that could be added by suite or class runners
        notifier.addListener(failureSpy);
        try {
            super.run(notifier);
        } finally {
            notifier.removeListener(failureSpy);
        }
    }
}
muni764
quelle
-15

Sie können dies auch versuchen:

@RunWith(JUnitParamsRunner.class)
public class AbstractTestClass {
  // some tests
}

@RunWith(MockitoJUnitRunner.class)
public class DatabaseModelTest extends AbstractTestClass {
  // some tests
}
Valentin Uveges
quelle
2
Dies funktioniert nicht, nur die Annotation der Unterklasse wird verarbeitet.
PaulNUK
funktioniert nicht - nur die Annotation von MockitoJUnitRunner wird berücksichtigt
Przemek Bielicki