Ich habe erst heute angefangen, etwas über Mockito zu lernen. Ich habe einen einfachen Test geschrieben (mit JUnit, siehe unten), aber ich kann nicht herausfinden, wie ich Scheinobjekte in Spring's verwalteten Beans verwenden kann. Was sind Best Practices für die Arbeit mit Spring ? Wie soll ich meiner Bohne eine verspottete Abhängigkeit hinzufügen?
Sie können dies bis zu meiner Frage überspringen .
Zuallererst, was ich gelernt habe. Dies ist ein sehr guter Artikel über Mocks Aren't Stubs , in dem die Grundlagen erläutert werden (Mock überprüft das Verhalten und nicht den Status ). Dann gibt es hier ein gutes Beispiel, Mockito, und hier, leichter mit Mockito verspotten . Wir haben eine Erklärung, dass Mockitos Scheinobjekte sowohl Schein- als auch Stummelobjekte 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 Mockito-Mocks in eine Spring Bean injizieren, versucht jemand, Springs zu verwenden ReflectionTestUtils.setField()
, aber hier haben wir Spring-Integrationstests, Erstellen von Mock-Objekten. Wir empfehlen, den Spring-Kontext zu ändern .
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 3 Beispiele für Code bereitstellen, um mich selbst zu verdeutlichen: Angenommen, wir haben Bean HelloWorld mit Methode printHello()
und Bean HelloFacade mit Methode sayHello
, die Aufrufe an die HelloWorld-Methode weiterleiten printHello()
.
Das erste Beispiel ist die Verwendung des Spring-Kontexts und ohne benutzerdefinierten Runner unter 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();
}
}
Wie @Noam hervorhob, gibt es eine Möglichkeit, es ohne expliziten Aufruf auszuführen MockitoAnnotations.initMocks(this);
. Ich werde in 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();
}
}
Ein anderer Weg, 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();
}
}
Nichts, dass wir in einem früheren Beispiel HelloFacadeImpl manuell instanziieren und HelloFacade zuweisen müssen, da HelloFacade eine Schnittstelle ist. Im letzten Beispiel können wir einfach HelloFacadeImpl deklarieren und Mokito wird es für uns instanziieren. Der Nachteil dieses Ansatzes besteht darin, dass die zu testende Einheit jetzt eine implizite Klasse und keine Schnittstelle ist.
quelle
@InjectMocks
(relativ neu, obwohl IIRC vor diesem Blog-Beitrag). Daher kann es vorkommen, dass die Bean-Definitionen neu angeordnet werden müssen. Ich bin mir nicht sicher, was die Frage letztendlich ist.Antworten:
Ehrlich gesagt bin ich mir nicht sicher, ob ich Ihre Frage wirklich verstehe: PI wird versuchen, so viel wie möglich zu klären, was ich aus Ihrer ursprünglichen Frage erhalte:
Erstens sollten Sie in den meisten Fällen KEINE Bedenken hinsichtlich des Frühlings haben. Beim Schreiben Ihres Unit-Tests muss selten eine Feder beteiligt sein. Im Normalfall müssen Sie nur das zu testende System (SUT, das zu testende Ziel) in Ihrem Komponententest instanziieren und auch Abhängigkeiten von SUT in den Test einfügen. Die Abhängigkeiten sind normalerweise ein Mock / Stub.
Ihr ursprünglich vorgeschlagener Weg und Beispiel 2, 3 machen genau das, was ich oben beschreibe.
In einigen seltenen Fällen (z. B. Integrationstests oder 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 Ihre SUT in Spring 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) Befolgen Sie die Anweisungen in Ihrem Link Spring Integration Tests, Erstellen von Scheinobjekten . Dieser Ansatz besteht darin, Mocks im App-Kontext von Spring zu erstellen, und Sie können das Mock-Objekt von der App ctx abrufen, 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 Wege sollten funktionieren. Der Hauptunterschied besteht darin, dass im ersteren Fall die Abhängigkeiten nach dem Durchlaufen des Lebenszyklus des Frühlings usw. (z. B. Bohneninitialisierung) injiziert werden, während im letzteren Fall vor den Händen injiziert wird. Wenn Ihr SUT beispielsweise die InitializingBean von spring implementiert und die Initialisierungsroutine die Abhängigkeiten umfasst, sehen Sie den Unterschied zwischen diesen beiden Ansätzen. Ich glaube, es gibt kein Richtig oder Falsch für diese beiden Ansätze, solange Sie wissen, was Sie tun.
Nur eine Ergänzung, @Mock, @Inject, MocktoJunitRunner usw. sind bei der Verwendung von Mockito nicht erforderlich. Sie sind nur Dienstprogramme, mit denen Sie die Eingabe von Mockito.mock (Foo.class) und einer Reihe von Setter-Aufrufen ersparen können.
quelle
@Autowired
und benutze@InjectMocks
, sehe ich die Bohnen mit Spring-Injektion, nicht die MocksTestTarget
. (Ich hatte gehofft, dass die Verwendung beider Annotationen nur ein Mock fürFoo
alle anderen Abhängigkeiten injizieren würde , aber dennoch die Standard-Beans mit Spring-Injection für alle anderen Abhängigkeiten verwenden würde, die automatisch verkabeltTestTarget
werden, aber im Integrationstest nicht verspottet werden. Keine Zigarre, wie es scheint. Frühling 3.1.2; Mockito 1.9.5)@Autowired
wird vom Spring JUnit Runner behandelt, der zuvor behandelt wurdesetup()
.@InjectMocks
wird vonMockitoAnnotaitons.initMocks(this)
in behandeltsetup()
. Ich sehe keinen Grund, damit aufzuhören. Ich kann es versuchen, um zu bestätigen, ob es funktioniert. :)MockitoAnnotations.initMocks(this)
in die aufgenommen@Before
. Auch das Entfernen@Autowired
(daher nur das@InjectMocks
Platzieren an Ort und Stelle) gibt mir den ScheinTestTarget
. (Durch das Entfernen bleiben jedoch@Autowired
auch alle anderen Bohnen bis zum Frühjahr nicht initialisiert.) Weitere Untersuchungen zeigen jedoch, dass ich eineTestTarget#setFoo(Foo f)
Methode benötige . Ohne@InjectMocks
funktioniert es gut, wenn nicht mit kombiniert@Autowired
. Also: Wenn Sie beide@Autowired
und verwenden@InjectMocks
,@Autowired private Foo mockFoo;
reicht dies nicht aus. Benötigt möglicherweise einen Fehlerbericht. werde untersuchen.@InjectMocks TestTarget sut
oder verwendet@Autowired @InjectMocks TestTarget sut
. Bei ersteren injiziert SpringTestTarget
(wie erwartet) nichts und es reicht aus, wennTestTarget
private Eigenschaften definiert werden, damit Mockito seine Bohnen injiziert. Für letztereTestTarget
braucht ein öffentlicher Setter für jede Bohne, die Mockito injizieren soll. Unter Verwendung des letzteren für ein Ziel , das sich nicht eine öffentliche Setter definierenFoo
wird mir gerade die Frühling injiziert Bohnen inTestTarget
, wo die Feder injiziertFoo
wird nicht durch die Mockito Mock ersetzt.Ihre Frage scheint zu fragen, welches der drei von Ihnen angegebenen Beispiele der bevorzugte Ansatz ist.
Beispiel 1 mit den Reflection TestUtils ist kein guter Ansatz für Unit-Tests . Sie möchten den Federkontext für einen Komponententest überhaupt nicht laden. Verspotten Sie einfach und injizieren Sie, was erforderlich ist, wie in Ihren anderen Beispielen gezeigt.
Sie möchten den Spring-Kontext laden, wenn Sie einige Integrationstests durchführen möchten. Ich würde es jedoch vorziehen
@RunWith(SpringJUnit4ClassRunner.class)
, das Laden des Kontexts zusammen mit zu verwenden,@Autowired
wenn Sie explizit Zugriff auf seine Beans benötigen.Beispiel 2 ist ein gültiger Ansatz, und durch die Verwendung von
@RunWith(MockitoJUnitRunner.class)
entfällt die Angabe einer @ Before-Methode und eines expliziten Aufrufs vonMockitoAnnotations.initMocks(this);
Beispiel 3 ist ein weiterer gültiger Ansatz, der nicht verwendet wird
@RunWith(...)
. Sie haben Ihre zu testende Klasse nichtHelloFacadeImpl
explizit instanziiert , aber Sie hätten dasselbe mit Beispiel 2 tun können.Mein Vorschlag ist, Beispiel 2 für Ihre Unit-Tests zu verwenden, da dies die Code-Unordnung verringert. Sie können auf die ausführlichere Konfiguration zurückgreifen, wenn Sie dazu gezwungen werden.
quelle
Mit der Einführung einiger neuer Testfunktionen in Spring 4.2.RC1 können Sie Spring-Integrationstests schreiben, die nicht auf dem basieren
SpringJUnit4ClassRunner
. Lesen Sie diesen Teil der Dokumentation.In Ihrem Fall könnten Sie Ihren Spring-Integrationstest schreiben und trotzdem folgende Mocks 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() { // .... } }
quelle
Sie brauchen das nicht wirklich,
MockitoAnnotations.initMocks(this);
wenn Sie Mockito 1.9 (oder neuer) verwenden - alles, was Sie brauchen, ist Folgendes:@InjectMocks private MyTestObject testObject; @Mock private MyDependentObject mockedObject;
Die
@InjectMocks
Annotation fügt demMyTestObject
Objekt alle Ihre Mocks hinzu .quelle
@RunWith(MockitoJUnitRunner.class)
entfällt die Notwendigkeit,MockitoAnnotations.initMocks(this)
explizit aufzurufen . Wie hier beschrieben, ist die Anmerkung seit 1.8.3Hier ist meine kurze Zusammenfassung.
Wenn Sie einen Komponententest schreiben möchten, verwenden Sie keinen Spring applicationContext, da in der Klasse, in der Sie den Komponententest durchführen, keine echten Abhängigkeiten eingefügt werden sollen. Verwenden Sie stattdessen Mocks, entweder mit der
@RunWith(MockitoJUnitRunner.class)
Annotation über der Klasse oder mitMockitoAnnotations.initMocks(this)
der Methode @Before.Wenn Sie einen Integrationstest schreiben möchten, verwenden Sie:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("yourTestApplicationContext.xml")
So richten Sie beispielsweise Ihren Anwendungskontext mit einer In-Memory-Datenbank ein. Normalerweise verwenden Sie in Integrationstests keine Mocks, aber Sie können dies mithilfe des
MockitoAnnotations.initMocks(this)
oben beschriebenen Ansatzes tun .quelle
Der Unterschied, ob Sie Ihr mit
@InjectMocks
Anmerkungen versehenes Feld instanziieren müssen, liegt in der Version von Mockito, nicht darin, ob Sie den MockitoJunitRunner oder verwendenMockitoAnnotations.initMocks
. In 1.9, das auch einige Konstruktorinjektionen Ihrer@Mock
Felder behandelt, wird die Instanziierung für Sie durchgeführt. In früheren Versionen müssen Sie es selbst instanziieren.So mache ich Unit-Tests meiner Spring Beans. Es gibt kein Problem. Menschen geraten in Verwirrung, wenn sie Spring-Konfigurationsdateien verwenden möchten, um die Mocks tatsächlich zu injizieren, was den Punkt von Unit-Tests und Integrationstests überschreitet.
Und natürlich ist das zu testende Gerät ein Impl. Sie müssen eine echte konkrete Sache testen, oder? Selbst wenn Sie es als Schnittstelle deklarieren würden, müssten Sie die reale Sache instanziieren, um es zu testen. Jetzt könnten Sie in Spione geraten, die Stub / Mock-Wrapper um echte Objekte sind, aber das sollte für Eckfälle sein.
quelle
Wenn Sie Ihr Projekt auf Spring Boot 1.4 migrieren würden, könnten Sie neue Anmerkungen
@MockBean
zum Fälschen verwendenMyDependentObject
. Mit dieser Funktion können Sie Mockitos@Mock
und@InjectMocks
Anmerkungen aus Ihrem Test entfernen .quelle