Ich möchte ein Mockito-Mock-Objekt in eine Spring (3+) -Bohne injizieren, um Unit-Tests mit JUnit durchzuführen. Meine Bean-Abhängigkeiten werden derzeit mithilfe der Annotation @Autowired
Für private Memberfelder eingefügt.
Ich habe überlegt, ReflectionTestUtils.setField
Zu verwenden, aber die Bean-Instanz, die ich einfügen möchte, ist tatsächlich ein Proxy und deklariert daher nicht die privaten Member-Felder der Zielklasse. Ich möchte keinen öffentlichen Setter für die Abhängigkeit erstellen, da ich meine Schnittstelle dann nur zu Testzwecken ändern werde.
Ich habe einige Ratschläge befolgt, die von der Spring-Community gegeben wurden, aber das Mock wird nicht erstellt und die automatische Verkabelung schlägt fehl:
<bean id="dao" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="com.package.Dao" />
</bean>
Der Fehler, auf den ich momentan stoße, ist folgender:
...
Caused by: org...NoSuchBeanDefinitionException:
No matching bean of type [com.package.Dao] found for dependency:
expected at least 1 bean which qualifies as autowire candidate for this dependency.
Dependency annotations: {
@org...Autowired(required=true),
@org...Qualifier(value=dao)
}
at org...DefaultListableBeanFactory.raiseNoSuchBeanDefinitionException(D...y.Java:901)
at org...DefaultListableBeanFactory.doResolveDependency(D...y.Java:770)
Wenn ich den Wert constructor-arg
Auf einen ungültigen Wert setze, tritt beim Starten des Anwendungskontexts kein Fehler auf.
Der beste Weg ist:
<bean id="dao" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="com.package.Dao" />
</bean>
pdate
In der Kontextdatei muss dieser Mock aufgeführt sein, bevor ein automatisch verdrahtetes Feld deklariert wird.
@InjectMocks
private MyTestObject testObject;
@Mock
private MyDependentObject mockedObject;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
Dadurch werden alle verspotteten Objekte in die Testklasse eingefügt. In diesem Fall wird mockedObject in das testObject eingefügt. Dies wurde oben erwähnt, aber hier ist der Code.
Ich habe eine sehr einfache Lösung mit Spring Java Config und Mockito:
@Configuration
public class TestConfig {
@Mock BeanA beanA;
@Mock BeanB beanB;
public TestConfig() {
MockitoAnnotations.initMocks(this); //This is a key
}
//You basically generate getters and add @Bean annotation everywhere
@Bean
public BeanA getBeanA() {
return beanA;
}
@Bean
public BeanB getBeanB() {
return beanB;
}
}
Gegeben:
@Service
public class MyService {
@Autowired
private MyDAO myDAO;
// etc
}
Sie können die zu testende Klasse über Autowiring laden lassen, die Abhängigkeit mit Mockito verspotten und dann Spring's ReflectionTestUtils verwenden, um die Verspottung in die zu testende Klasse zu injizieren.
@ContextConfiguration(classes = { MvcConfiguration.class })
@RunWith(SpringJUnit4ClassRunner.class)
public class MyServiceTest {
@Autowired
private MyService myService;
private MyDAO myDAOMock;
@Before
public void before() {
myDAOMock = Mockito.mock(MyDAO.class);
ReflectionTestUtils.setField(myService, "myDAO", myDAOMock);
}
// etc
}
Bitte beachten Sie, dass diese Methode vor Spring 4.3.1 nicht mit Diensten hinter einem Proxy funktioniert (mit @Transactional
oder Cacheable
, zum Beispiel). Dies wurde durch SPR-1405 behoben.
Für frühere Versionen besteht eine Lösung darin, den Proxy zu entpacken, wie dort beschrieben: Transaktionsanmerkung verhindert, dass Dienste verspottet werden (das ist, was ReflectionTestUtils.setField
jetzt standardmäßig)
Wenn Sie Spring Boot 1.4 verwenden, haben Sie eine großartige Möglichkeit, dies zu tun. Verwenden Sie einfach die neue Marke @SpringBootTest
Für Ihre Klasse und @MockBean
Für das Feld, und Spring Boot erstellt ein Modell dieses Typs und fügt es in den Kontext ein (anstatt das Original einzufügen):
@RunWith(SpringRunner.class)
@SpringBootTest
public class MyTests {
@MockBean
private RemoteService remoteService;
@Autowired
private Reverser reverser;
@Test
public void exampleTest() {
// RemoteService has been injected into the reverser bean
given(this.remoteService.someCall()).willReturn("mock");
String reverse = reverser.reverseSomeCall();
assertThat(reverse).isEqualTo("kcom");
}
}
Wenn Sie jedoch Spring Boot nicht verwenden oder eine frühere Version verwenden, müssen Sie etwas mehr tun:
Erstellen Sie eine @Configuration
- Bean, die Ihre Mocks in den Spring-Kontext einfügt:
@Configuration
@Profile("useMocks")
public class MockConfigurer {
@Bean
@Primary
public MyBean myBeanSpy() {
return mock(MyBean.class);
}
}
Mit der Annotation @Primary
Teilen Sie spring mit, dass diese Bean Priorität hat, wenn kein Qualifier angegeben ist.
Stellen Sie sicher, dass Sie die Klasse mit @Profile("useMocks")
kommentieren, um zu steuern, welche Klassen den Schein verwenden und welche die echte Bean verwenden.
Aktivieren Sie schließlich in Ihrem Test das Profil userMocks
:
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = {Application.class})
@WebIntegrationTest
@ActiveProfiles(profiles={"useMocks"})
public class YourIntegrationTestIT {
@Inject
private MyBean myBean; //It will be the mock!
@Test
public void test() {
....
}
}
Wenn Sie nicht den Schein, sondern die echte Bohne verwenden möchten, aktivieren Sie einfach das useMocks
-Profil nicht:
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = {Application.class})
@WebIntegrationTest
public class AnotherIntegrationTestIT {
@Inject
private MyBean myBean; //It will be the real implementation!
@Test
public void test() {
....
}
}
Seit 1.8. Mockito hat @InjectMocks
- das ist unglaublich nützlich. Meine JUnit-Tests sind @RunWith
Die MockitoJUnitRunner
und ich erstelle @Mock
Objekte, die alle Abhängigkeiten für die getestete Klasse erfüllen, die alle injiziert werden, wenn das private Mitglied mit @InjectMocks
.
Ich habe @RunWith
Den SpringJUnit4Runner
Erst jetzt für Integrationstests.
Ich werde bemerken, dass es nicht in der Lage zu sein scheint, List<T>
Auf dieselbe Weise wie Spring zu injizieren. Es wird nur nach einem Mock-Objekt gesucht, das List
erfüllt, und es wird keine Liste von Mock-Objekten eingefügt. Die Problemumgehung bestand für mich darin, ein @Spy
Für eine manuell instanziierte Liste zu verwenden und die Scheinobjekte für Komponententests manuell zu dieser Liste hinzuzufügen. Vielleicht war das absichtlich so, denn es zwang mich mit Sicherheit, genau darauf zu achten, was zusammen verspottet wurde.
Update: Es gibt jetzt bessere, sauberere Lösungen für dieses Problem. Bitte beachten Sie zuerst die anderen Antworten.
Ich fand schließlich eine Antwort von Ronen auf seinem Blog. Das Problem, das ich hatte, ist auf die Methode Mockito.mock(Class c)
zurückzuführen, die den Rückgabetyp Object
deklariert. Infolgedessen kann Spring den Bean-Typ nicht aus dem Rückgabetyp der Factory-Methode ableiten.
Ronens Lösung ist das Erstellen einer FactoryBean
-Implementierung, die Mocks zurückgibt. Über die Schnittstelle FactoryBean
kann Spring den Typ der von der Factory-Bean erstellten Objekte abfragen.
Meine verspottete Bohnendefinition sieht jetzt so aus:
<bean id="mockDaoFactory" name="dao" class="com.package.test.MocksFactory">
<property name="type" value="com.package.Dao" />
</bean>
Ab Spring 3.2 ist dies kein Problem mehr. Spring unterstützt jetzt die automatische Verdrahtung der Ergebnisse generischer Factory-Methoden. Siehe den Abschnitt mit dem Titel "Generic Factory Methods" in diesem Blog-Beitrag: http://spring.io/blog/2012/11/07/spring-framework-3-2-rc1-new-testing-features/ .
Der entscheidende Punkt ist:
In Spring 3.2 werden generische Rückgabetypen für Factory-Methoden nun ordnungsgemäß abgeleitet, und das automatische Verdrahten nach Typ für Mocks sollte wie erwartet funktionieren. Benutzerdefinierte Workarounds wie MockitoFactoryBean, EasyMockFactoryBean oder Springockito sind daher wahrscheinlich nicht mehr erforderlich.
Das heißt, dies sollte sofort funktionieren:
<bean id="dao" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="com.package.Dao" />
</bean>
Wenn Sie eine Feder> = 3.0 verwenden, versuchen Sie es mit Federn @Configuration
Annotation, um einen Teil des Anwendungskontexts zu definieren
@Configuration
@ImportResource("com/blah/blurk/rest-of-config.xml")
public class DaoTestConfiguration {
@Bean
public ApplicationService applicationService() {
return mock(ApplicationService.class);
}
}
Wenn Sie die @ImportResource nicht verwenden möchten, können Sie auch umgekehrt vorgehen:
<beans>
<!-- rest of your config -->
<!-- the container recognize this as a Configuration and adds it's beans
to the container -->
<bean class="com.package.DaoTestConfiguration"/>
</beans>
Weitere Informationen finden Sie unter spring-framework-reference: Java-basierte Containerkonfiguration
Der folgende Code funktioniert mit Autodraht - er ist nicht die kürzeste Version, aber nützlich, wenn er nur mit Standard-Spring-/Mockito-Gläsern funktionieren soll.
<bean id="dao" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target"> <bean class="org.mockito.Mockito" factory-method="mock"> <constructor-arg value="com.package.Dao" /> </bean> </property>
<property name="proxyInterfaces"> <value>com.package.Dao</value> </property>
</bean>
Vielleicht nicht die perfekte Lösung, aber ich benutze keine Feder, um DI für Komponententests durchzuführen. Die Abhängigkeiten für eine einzelne Bean (die zu testende Klasse) sind normalerweise nicht allzu komplex, daher führe ich die Injektion direkt im Testcode durch.
Mit Mockito kann ich Folgendes tun:
<bean id="stateMachine" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="com.abcd.StateMachine"/>
</bean>
Posting ein paar Beispiele auf der Grundlage der oben genannten Ansätze
Mit dem Frühling:
@ContextConfiguration(locations = { "classpath:context.xml" })
@RunWith(SpringJUnit4ClassRunner.class)
public class TestServiceTest {
@InjectMocks
private TestService testService;
@Mock
private TestService2 testService2;
}
Ohne Frühling:
@RunWith(MockitoJUnitRunner.class)
public class TestServiceTest {
@InjectMocks
private TestService testService = new TestServiceImpl();
@Mock
private TestService2 testService2;
}
Update - neue Antwort hier: https://stackoverflow.com/a/19454282/411229 . Diese Antwort gilt nur für diejenigen in Spring-Versionen vor 3.2.
Ich habe eine Weile nach einer definitiveren Lösung dafür gesucht. Dieser Blogeintrag scheint alle meine Bedürfnisse zu decken und ist nicht auf die Bestellung von Bohnenerklärungen angewiesen. Alle Ehre gebührt Mattias Severson. http://www.jayway.com/2011/11/30/spring-integration-tests-part-i-creating-mock-objects/
Implementieren Sie grundsätzlich ein FactoryBean
package com.jayway.springmock;
import org.mockito.Mockito;
import org.springframework.beans.factory.FactoryBean;
/**
* A {@link FactoryBean} for creating mocked beans based on Mockito so that they
* can be {@link @Autowired} into Spring test configurations.
*
* @author Mattias Severson, Jayway
*
* @see FactoryBean
* @see org.mockito.Mockito
*/
public class MockitoFactoryBean<T> implements FactoryBean<T> {
private Class<T> classToBeMocked;
/**
* Creates a Mockito mock instance of the provided class.
* @param classToBeMocked The class to be mocked.
*/
public MockitoFactoryBean(Class<T> classToBeMocked) {
this.classToBeMocked = classToBeMocked;
}
@Override
public T getObject() throws Exception {
return Mockito.mock(classToBeMocked);
}
@Override
public Class<?> getObjectType() {
return classToBeMocked;
}
@Override
public boolean isSingleton() {
return true;
}
}
Als nächstes aktualisieren Sie Ihre Federkonfiguration mit den folgenden Schritten:
<beans...>
<context:component-scan base-package="com.jayway.example"/>
<bean id="someDependencyMock" class="com.jayway.springmock.MockitoFactoryBean">
<constructor-arg name="classToBeMocked" value="com.jayway.example.SomeDependency" />
</bean>
</beans>
Wenn ich mir Springockito-Entwicklungstempo und Anzahl offener Probleme anschaue, wäre ich ein bisschen besorgt, wenn ich es heutzutage in meinen Test-Suite-Stack aufnehmen könnte. Die Tatsache, dass das letzte Release vor dem Release von Spring 4 erstellt wurde, wirft Fragen auf wie "Ist es möglich, es einfach in Spring 4 zu integrieren?". Ich weiß es nicht, weil ich es nicht ausprobiert habe. Ich bevorzuge den reinen Spring-Ansatz, wenn ich Spring Bean im Integrationstest verspotten muss.
Es besteht die Möglichkeit, Spring Beans mit einfachen Spring-Features zu fälschen. Sie müssen @Primary
, @Profile
und @ActiveProfiles
Anmerkungen dazu. Ich habe einen Blogbeitrag zum Thema geschrieben.
Ich würde vorschlagen, Ihr Projekt auf Spring Boot 1.4 zu migrieren. Danach können Sie eine neue Annotation verwenden @MockBean
um Ihren com.package.Dao
Zu fälschen
Ich entwickelte eine Lösung auf der Grundlage des Vorschlags von Kresimir Nesek. Ich habe eine neue Annotation @ EnableMockedBean hinzugefügt, um den Code etwas übersichtlicher und modularer zu gestalten.
@EnableMockedBean
@SpringBootApplication
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes=MockedBeanTest.class)
public class MockedBeanTest {
@MockedBean
private HelloWorldService helloWorldService;
@Autowired
private MiddleComponent middleComponent;
@Test
public void helloWorldIsCalledOnlyOnce() {
middleComponent.getHelloMessage();
// THEN HelloWorldService is called only once
verify(helloWorldService, times(1)).getHelloMessage();
}
}
Ich habe ein post geschrieben, das es erklärt.
Ich benutze eine Kombination aus dem Ansatz von Markus T und einer einfachen Hilfsimplementierung von ImportBeanDefinitionRegistrar
, die nach einer benutzerdefinierten Annotation (@MockedBeans
) Sucht, in der angegeben werden kann, welche Klassen verspottet werden sollen . Ich glaube, dass dieser Ansatz zu einem prägnanten Komponententest führt, bei dem ein Teil des Kesselschild-Codes in Bezug auf das Verspotten entfernt wurde.
So sieht ein Beispiel-Unit-Test mit diesem Ansatz aus:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader=AnnotationConfigContextLoader.class)
public class ExampleServiceIntegrationTest {
//our service under test, with mocked dependencies injected
@Autowired
ExampleService exampleService;
//we can autowire mocked beans if we need to used them in tests
@Autowired
DependencyBeanA dependencyBeanA;
@Test
public void testSomeMethod() {
...
exampleService.someMethod();
...
verify(dependencyBeanA, times(1)).someDependencyMethod();
}
/**
* Inner class configuration object for this test. Spring will read it thanks to
* @ContextConfiguration(loader=AnnotationConfigContextLoader.class) annotation on the test class.
*/
@Configuration
@Import(TestAppConfig.class) //TestAppConfig may contain some common integration testing configuration
@MockedBeans({DependencyBeanA.class, DependencyBeanB.class, AnotherDependency.class}) //Beans to be mocked
static class ContextConfiguration {
@Bean
public ExampleService exampleService() {
return new ExampleService(); //our service under test
}
}
}
Um dies zu erreichen, müssen Sie zwei einfache Hilfsklassen definieren - eine benutzerdefinierte Annotation (@MockedBeans
) Und eine benutzerdefinierte ImportBeanDefinitionRegistrar
-Implementierung. Die Annotationsdefinition @MockedBeans
Muss mit @Import(CustomImportBeanDefinitionRegistrar.class)
annotiert werden, und die ImportBeanDefinitionRgistrar
muss der Konfiguration in ihrer registerBeanDefinitions
-Methode verspottete Beans-Definitionen hinzufügen.
Wenn Sie den Ansatz mögen, finden Sie Beispiel Implementierungen auf meinem Blogpost .
Ich fand eine ähnliche Antwort wie Teabot, um eine MockFactory zu erstellen, die die Mocks liefert. Ich habe das folgende Beispiel verwendet, um die Mock-Factory zu erstellen (da der Link zu narkisr nicht mehr vorhanden ist): http://hg.randompage.org/Java/src/407e78aa08a0/projects/bookmarking/backend/spring/src/ test/Java/org/randompage/bookmarking/backend/testUtils/MocksFactory.Java
<bean id="someFacade" class="nl.package.test.MockFactory">
<property name="type" value="nl.package.someFacade"/>
</bean>
Dies hilft auch zu verhindern, dass Spring die Injektionen aus der verspotteten Bohne auflösen möchte.
<bean id="mockDaoFactory" name="dao" class="com.package.test.MocksFactory">
<property name="type" value="com.package.Dao" />
</bean>
dies funktioniert sehr gut, wenn es zuerst/früh in der XML-Datei deklariert wird. Mockito 1.9.0/Spring 3.0.5
Heute fand ich heraus, dass ein Frühlingskontext, in dem ich vor den Mockito-Bohnen einen deklarierte, nicht geladen werden konnte. Nach dem Verschieben des AFTER the Mocks wurde der App-Kontext erfolgreich geladen. Sich kümmern :)
Für die Aufzeichnung, alle meine Tests funktionieren korrekt, indem das Gerät nur verzögert initialisiert wird, z.
<bean id="fixture"
class="it.tidalwave.northernwind.rca.embeddedserver.impl.DefaultEmbeddedServer"
lazy-init="true" /> <!-- To solve Mockito + Spring problems -->
<bean class="it.tidalwave.messagebus.aspect.spring.MessageBusAdapterFactory" />
<bean id="applicationMessageBus"
class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="it.tidalwave.messagebus.MessageBus" />
</bean>
<bean class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="javax.servlet.ServletContext" />
</bean>
Ich nehme an, das Grundprinzip ist das, was Mattias erklärt hier (am Ende des Beitrags), dass eine Problemumgehung die Reihenfolge ändert, in der die Beans deklariert werden das Ende.