it-swarm.com.de

Injizieren von Mockito-Verspottungen in eine Frühlingsbohne

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.

268
teabot

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.

125
amra
@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.

107
Greg Beauchamp

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;
    }
}
62
Piotr Gwiazda

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)

41
Paul Croarkin

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() {
        ....
    }
}
34
jfcorugedo

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.

19
Doug Moscrop

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>
13
teabot

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>
11
Ryan Walls

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

9
Markus T

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> 
9
Kamil

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.

8
Angelo Genovese

Mit Mockito kann ich Folgendes tun:

<bean id="stateMachine" class="org.mockito.Mockito" factory-method="mock">
    <constructor-arg value="com.abcd.StateMachine"/>
</bean>
7
Alexander

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;
}
6
Basu

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>
2
Ryan Walls

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.

2
luboskrnac

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

1
luboskrnac

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.

1
Alfredo Diaz

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 .

1
Kresimir Nesek

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.

1
Renso Lohuis
<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

1

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 :)

0

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.

0