webentwicklung-frage-antwort-db.com.de

Mockito, JUnit und Frühling

Ich habe erst heute angefangen, etwas über Mockito zu lernen. Ich habe einige einfache Tests geschrieben (mit JUnit, siehe unten), aber ich kann nicht herausfinden, wie ich Scheinobjekte in den verwalteten Beans von Spring verwenden kann. Was ist Best Practices für die Arbeit mit Spring. Wie sollte ich meiner Bean eine gespielte Abhängigkeit hinzufügen?

Sie können dies bis zurück zu meiner Frage überspringen.

Vor allem, was ich gelernt habe ... Das ist ein sehr guter Artikel Mocks sind keine Stubs das erklärt die Grundlagen (Mocks Checks Verhaltensüberprüfung nicht Statusüberprüfung) . Dann gibt es ein gutes Beispiel hier Mockito und hier Leichteres Spott mit Mockito . Wir haben erklärt, dass die Scheinobjekte von Mockito sowohl Schein als auch Stub sind. 

Hier Mockito und hier Matchers finden Sie weitere Beispiele. 

Dieser Test

@Test
public void testReal(){
    List<String> mockedList = mock(List.class);
     //stubbing
     //when(mockedList.get(0)).thenReturn("first");

    mockedList.get(anyInt());
    OngoingStubbing<String> stub= when(null);
    stub.thenReturn("first");

    //String res = mockedList.get(0);
                //System.out.println(res);

     //you can also verify using argument matcher
     //verify(mockedList).get(anyInt());

    verify(mockedList);
    mockedList.get(anyInt());
}

funktioniert gut.

Zurück zu meiner Frage. Hier Injizieren von Mockito spottet in eine Springbohne jemand versucht, Federn ReflectionTestUtils.setField() zu verwenden, aber hier Spring Integrationstests, Erstellen von Scheinobjekten Wir haben die Empfehlung, change Spring Kontext.

Ich habe die letzten beiden Links nicht wirklich verstanden ... Kann mir jemand erklären, welches Problem Spring mit Mockito hat? Was ist los mit dieser Lösung?

@InjectMocks
private MyTestObject testObject

@Mock
private MyDependentObject mockedObject

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

https://stackoverflow.com/a/8742745/1137529

EDIT: Ich war nicht wirklich klar. Ich werde drei Codebeispiele zur Verdeutlichung meines Selbst bereitstellen: Angenommen, wir haben Bean HelloWorld mit der Methode printHello() und Bean HelloFacade mit der Methode sayHello, die Aufrufe an die HelloWorld-Methode printHello() weiterleitet.

Das erste Beispiel ist die Verwendung des Spring-Kontexts und ohne benutzerdefinierten Läufer und die Verwendung von ReflectionTestUtils für die Abhängigkeitsinjektion (DI):

public class Hello1Test  {
private ApplicationContext ctx;

@Before
public void setUp() {
    MockitoAnnotations.initMocks(this);
    this.ctx = new ClassPathXmlApplicationContext("META-INF/spring/ServicesImplContext.xml");
}



@Test
public void testHelloFacade() {
    HelloFacade obj = (HelloFacade) ctx.getBean(HelloFacadeImpl.class);
    HelloWorld mock = mock(HelloWorld.class);
    doNothing().when(mock).printHello();

    ReflectionTestUtils.setField(obj, "hello", mock);
    obj.sayHello();

    verify(mock, times(1)).printHello();
}

}

@Noam wies darauf hin, dass es eine Möglichkeit gibt, es ohne expliziten Aufruf von MockitoAnnotations.initMocks(this); auszuführen. Ich werde an diesem Beispiel auch den Kontext des Frühlings verwenden.

@RunWith(MockitoJUnitRunner.class)
public class Hello1aTest {


@InjectMocks
private HelloFacade obj =  new HelloFacadeImpl();

@Mock
private HelloWorld mock;


@Test
public void testHelloFacade() {
    doNothing().when(mock).printHello();
    obj.sayHello();
}

}

Eine andere Möglichkeit, dies zu tun

public class Hello1aTest {

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


@InjectMocks
private HelloFacadeImpl obj;

@Mock
private HelloWorld mock;


@Test
public void testHelloFacade() {
    doNothing().when(mock).printHello();
    obj.sayHello();
}

}

Nicht, dass wir in einem früheren Beispiel HelloFacadeImpl manuell instanieren müssen und es HelloFacade zuweisen müssen, wobei beacuse HelloFacade die Schnittstelle ist. Im letzten Beispiel können wir HelloFacadeImpl nur deklarieren und Mokito wird es für uns instanziieren. Der Nachteil dieses Ansatzes besteht darin, dass die getestete Einheit nun eine Impl-Klasse und keine Schnittstelle ist.

73
alexsmail

Ehrlich gesagt bin ich mir nicht sicher, ob ich Ihre Frage wirklich verstehe: P Ich werde versuchen, so viel wie möglich zu klären, was ich aus Ihrer ursprünglichen Frage bekomme:

Erstens sollten Sie in den meisten Fällen KEINE Bedenken hinsichtlich des Frühlings haben. Beim Schreiben des Komponententests brauchen Sie selten Feder. Im Normalfall müssen Sie lediglich das zu testende System (SUT, das zu testende Ziel) in Ihrem Gerätetest instanziieren und auch Abhängigkeiten von SUT in den Test einfügen. Die Abhängigkeiten sind normalerweise ein Schein/Stub.

Ihr ursprünglich vorgeschlagener Weg und Beispiel 2, 3 tun genau das, was ich oben beschreibe.

In seltenen Fällen (z. B. Integrationstests oder einige spezielle Komponententests) müssen Sie einen Spring-App-Kontext erstellen und Ihr SUT aus dem App-Kontext abrufen. In einem solchen Fall glaube ich, dass Sie:

1) Erstellen Sie Ihr SUT im Frühjahr-App-ctx, beziehen Sie sich darauf und injizieren Sie Mocks

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("test-app-ctx.xml")
public class FooTest {

    @Autowired
    @InjectMocks
    TestTarget sut;

    @Mock
    Foo mockFoo;

    @Before
    /* Initialized mocks */
    public void setup() {
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void someTest() {
         // ....
    }
}

oder 

2) Folgen Sie dem in Ihrem Link beschriebenen Weg Spring Integration Tests, Mock-Objekte erstellen . Mit diesem Ansatz können Sie Mocks im App-Kontext von Spring erstellen. Sie können das Mock-Objekt von der App ctx erhalten, um Ihre Stubbing/Verifizierung durchzuführen:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("test-app-ctx.xml")
public class FooTest {

    @Autowired
    TestTarget sut;

    @Autowired
    Foo mockFoo;

    @Test
    public void someTest() {
         // ....
    }
}

Beide Möglichkeiten sollten funktionieren. Der Hauptunterschied besteht darin, dass im ersteren Fall die Abhängigkeiten nach dem Lebenszyklus des Frühlings usw. (z. B. Bohneninitialisierung) eingespritzt werden, während der letztere Fall vor den Händen injiziert wird. Wenn Ihr SUT beispielsweise die InitializingBean-Methode von Spring implementiert und die Initialisierungsroutine die Abhängigkeiten beinhaltet, werden Sie den Unterschied zwischen diesen beiden Ansätzen feststellen. Ich glaube, dass es für diese beiden Ansätze kein Recht oder Unrecht gibt, solange Sie wissen, was Sie tun.

Nur eine Ergänzung, @Mock, @Inject, MocktoJunitRunner usw. sind bei der Verwendung von Mockito nicht erforderlich. Sie sind lediglich Hilfsprogramme, um das Schreiben des Mockito.mock (Foo.class) und eine Reihe von Setzeraufrufen zu ersparen.

50
Adrian Shum

Ihre Frage scheint zu fragen, welches der drei Beispiele Sie bevorzugen.

Beispiel 1 Die Verwendung der Reflection TestUtils ist kein guter Ansatz für Unit-Tests. Sie möchten wirklich nicht den Federkontext für einen Komponententest laden. Verspotten Sie einfach und injizieren Sie, was in Ihren anderen Beispielen gezeigt wird. 

Sie möchten den Spring-Kontext laden, wenn Sie Integration testing ausführen möchten. Ich würde jedoch @RunWith(SpringJUnit4ClassRunner.class) vorziehen, um das Laden des Kontexts zusammen mit @Autowired auszuführen, wenn Sie explizit auf seine Beans zugreifen möchten.

Beispiel 2 ist ein gültiger Ansatz und die Verwendung von @RunWith(MockitoJUnitRunner.class) macht die Angabe einer @Before-Methode und einen expliziten Aufruf von MockitoAnnotations.initMocks(this); überflüssig.

Beispiel 3 ist ein anderer gültiger Ansatz, der @RunWith(...) nicht verwendet. Sie haben die zu testende Klasse HelloFacadeImpl nicht explizit instanziiert, aber Sie könnten dasselbe mit Beispiel 2 gemacht haben.

Mein Vorschlag ist, Beispiel 2 für das Testen von Einheiten zu verwenden, da dadurch der Code-Durcheinander reduziert wird. Sie können auf die ausführlichere Konfiguration zurückgreifen, wenn Sie dazu gezwungen werden.

6
Brad

Mit der Einführung einiger neuer Testeinrichtungen in Spring 4.2.RC1 können Spring Integrationstests geschrieben werden, die nicht auf dem SpringJUnit4ClassRunner basieren. Check out this Teil der Dokumentation.

In Ihrem Fall könnten Sie Ihren Spring-Integrationstest schreiben und trotzdem Mocks wie folgt verwenden:

@RunWith(MockitoJUnitRunner.class)
@ContextConfiguration("test-app-ctx.xml")
public class FooTest {

    @ClassRule
    public static final SpringClassRule SPRING_CLASS_RULE = new SpringClassRule();

    @Rule
    public final SpringMethodRule springMethodRule = new SpringMethodRule();

    @Autowired
    @InjectMocks
    TestTarget sut;

    @Mock
    Foo mockFoo;

    @Test
    public void someTest() {
         // ....
    }
}
4
geoand

Hier ist meine kurze Zusammenfassung. 

Wenn Sie einen Komponententest schreiben möchten, verwenden Sie keinen Spring applicationContext, da keine realen Abhängigkeiten in die Klasse eingefügt werden sollen, in der Sie den Komponententest durchführen. Verwenden Sie stattdessen Mocks, entweder mit der Annotation @RunWith(MockitoJUnitRunner.class) oberhalb der Klasse oder mit MockitoAnnotations.initMocks(this) in der Methode @Before.

Wenn Sie einen Integrationstest schreiben möchten, verwenden Sie:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("yourTestApplicationContext.xml")

So richten Sie Ihren Anwendungskontext mit einer In-Memory-Datenbank ein, zum Beispiel ..__ Normalerweise verwenden Sie Mocks nicht in Integrationstests, aber Sie können dies mit dem oben beschriebenen Ansatz von MockitoAnnotations.initMocks(this) tun.

2
Adriaan Koster

Sie benötigen die MockitoAnnotations.initMocks(this); nicht wirklich, wenn Sie mockito 1.9 (oder neuer) verwenden.

@InjectMocks
private MyTestObject testObject;

@Mock
private MyDependentObject mockedObject;

Die @InjectMocks-Anmerkung fügt alle Ihre Mocks in das MyTestObject-Objekt ein.

2
Noam

Wenn Sie Ihr Projekt nach Spring Boot 1.4 migrieren, können Sie die neue Annotation @MockBean zum Fälschen von MyDependentObject verwenden. Mit dieser Funktion können Sie die Annotationen von Mockito @Mock und @InjectMocks aus Ihrem Test entfernen. 

0
luboskrnac

Der Unterschied, ob Sie Ihr @InjectMocks-Anmerkungsfeld instanziieren müssen, liegt in der Version von Mockito und nicht darin, ob Sie den MockitoJunitRunner oder MockitoAnnotations.initMocks verwenden. In 1.9, das auch einige Konstruktorinjektionen Ihrer @Mock-Felder behandelt, übernimmt es die Instantiierung für Sie. In früheren Versionen müssen Sie es selbst instanziieren.

So mache ich Unit-Tests meiner Spring-Bohnen. Es gibt kein Problem. Die Leute geraten in Verwirrung, wenn sie Spring-Konfigurationsdateien verwenden möchten, um tatsächlich die Mocks zu injizieren, was den Punkt von Unit-Tests und Integrationstests überschreitet.

Und natürlich das zu testende Gerät ist ein Impl. Sie müssen eine konkrete Sache testen, oder? Selbst wenn Sie es als Schnittstelle deklariert haben, müssten Sie die echte Instanz instanziieren, um es zu testen. Jetzt könnten Sie sich in Spione stürzen, die Stub/Mock-Wrapper für echte Objekte sind, aber das sollte für Eckfälle sein.

0
jhericks