Programing

봄 콩에 Mockito 모의 주입

lottogame 2020. 3. 26. 08:19
반응형

봄 콩에 Mockito 모의 주입


JUnit을 사용한 단위 테스트를 위해 Mockito 모의 객체를 Spring (3+) Bean에 주입하고 싶습니다. 내 Bean 종속성은 현재 @Autowired개인 멤버 필드 주석을 사용하여 주입됩니다 .

사용을 고려 ReflectionTestUtils.setField했지만 주입하려는 Bean 인스턴스는 실제로 프록시이므로 대상 클래스의 개인 멤버 필드를 선언하지 않습니다. 테스트 목적으로 인터페이스를 순수하게 수정하기 때문에 종속성에 대한 공용 세터를 만들고 싶지 않습니다.

Spring 커뮤니티에서 제공 한 조언 을 따랐 지만 모의가 생성되지 않고 자동 배선이 실패합니다.

<bean id="dao" class="org.mockito.Mockito" factory-method="mock">
    <constructor-arg value="com.package.Dao" />
</bean>

현재 발생하는 오류는 다음과 같습니다.

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

constructor-arg값을 잘못된 것으로 설정 하면 응용 프로그램 컨텍스트를 시작할 때 오류가 발생하지 않습니다.


가장 좋은 방법은 다음과 같습니다.

<bean id="dao" class="org.mockito.Mockito" factory-method="mock"> 
    <constructor-arg value="com.package.Dao" /> 
</bean> 

업데이트
컨텍스트 파일에서이 모형은 선언 된 자동 와이어 필드보다 먼저 나열되어야합니다.


@InjectMocks
private MyTestObject testObject;

@Mock
private MyDependentObject mockedObject;

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

이렇게하면 조롱 된 객체가 테스트 클래스에 주입됩니다. 이 경우 mockedObject를 testObject에 주입합니다. 이것은 위에서 언급되었지만 여기에 코드가 있습니다.


Spring Java Config 및 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;
    }
}

주어진:

@Service
public class MyService {
    @Autowired
    private MyDAO myDAO;

    // etc
}

자동 배선을 통해 테스트중인 클래스를로드하고 Mockito와의 종속성을 모의 한 다음 Spring의 ReflectionTestUtils를 사용하여 테스트중인 클래스에 모의를 주입 할 수 있습니다.

@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
}

Spring 4.3.1 이전에는이 ​​메소드가 프록시 뒤의 서비스 ( 예 : @Transactional, 또는 Cacheable)로 작동하지 않습니다 . 이것은 SPR-14050에 의해 수정되었습니다 .

이전 버전의 경우 해결책은 다음과 같이 프록시 풀기를 해제하는 것입니다. 트랜잭션 주석은 서비스가 조롱되는 것을 방지합니다 ( ReflectionTestUtils.setField현재 기본적으로 수행되는 작업).


Spring Boot 1.4를 사용하는 경우이 작업을 수행하는 멋진 방법이 있습니다. @SpringBootTest수업과 @MockBean현장 에서 새로운 브랜드 사용하기 만하면 Spring Boot는이 유형의 모형을 만들어 문맥에 주입합니다 (원래 브랜드 주입하는 대신).

@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");
    }

}

반면에 Spring Boot를 사용하지 않거나 이전 버전을 사용하는 경우 약간 더 많은 작업을 수행해야합니다.

@ConfigurationSpring 컨텍스트에 모의 객체를 주입 하는 Bean을 작성하십시오 .

@Configuration
@Profile("useMocks")
public class MockConfigurer {

    @Bean
    @Primary
    public MyBean myBeanSpy() {
        return mock(MyBean.class);
    }
}

@Primary주석을 사용하면 한정자가 지정되지 않은 경우이 bean이 우선 순위를 갖도록 스프링에 알립니다.

@Profile("useMocks")모의를 사용할 클래스와 실제 Bean을 사용할 클래스를 제어 하려면 클래스에 주석을 달아야합니다 .

마지막으로 테스트에서 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() {
        ....
    }
}

mock을 사용하고 싶지 않고 실제 bean을 사용하고 싶다면 useMocks프로파일을 활성화하지 마십시오 .

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

Mockito는 1.8.3 부터 @InjectMocks-이것은 매우 유용합니다. 내 JUnit 테스트는 @RunWithMockitoJUnitRunner나는 구축 @Mock개인 회원이 주석 때 모든 주입되는 클래스에 대한 모든 종속성이 테스트되고 만족 객체 @InjectMocks.

I 만 지금은 통합 테스트합니다.@RunWithSpringJUnit4Runner

List<T>스프링과 같은 방식으로 주사 할 수없는 것 같습니다 . 를 만족하는 Mock 객체 만 찾고 Mock 객체 List목록을 삽입하지 않습니다. 해결 방법 @Spy은 수동으로 인스턴스화 된 목록에 대해 를 사용하고 단위 테스트를 위해 모의 객체를 해당 목록에 수동으로 추가하는 것입니다. 어쩌면 그것은 의도적이었습니다. 왜냐하면 그것은 함께 조롱 된 것에주의를 기울여야했기 때문입니다.


업데이트 : 이제이 문제에 대한 더 나은 해결책이 있습니다. 다른 답변을 먼저 고려하십시오.

나는 결국 그의 블로그에서 ronen이 이에 대한 답변을 찾았습니다. 내가 겪고있는 문제는 Mockito.mock(Class c)의 반환 유형을 선언하는 메서드 때문 입니다 Object. 결과적으로 Spring은 팩토리 메소드 리턴 유형에서 Bean 유형을 유추 할 수 없습니다.

Ronen의 솔루션FactoryBean모의를 반환 하는 구현 을 만드는 것입니다. FactoryBean인터페이스를 통해 Spring은 팩토리 빈에 의해 생성 된 객체의 유형을 쿼리 할 수 ​​있습니다.

내 조롱 된 콩 정의는 이제 다음과 같습니다.

<bean id="mockDaoFactory" name="dao" class="com.package.test.MocksFactory">
    <property name="type" value="com.package.Dao" />
</bean>

Spring 3.2부터는 더 이상 문제가되지 않습니다. Spring은 이제 일반적인 팩토리 메소드 결과의 자동 배선을 지원합니다. 이 블로그 게시물 ( http://spring.io/blog/2012/11/07/spring-framework-3-2-rc1-new-testing-features/) 에서 "Generic Factory Methods"라는 제목의 섹션을 참조 하십시오 .

핵심은 다음과 같습니다.

Spring 3.2에서는 팩토리 메소드의 일반 리턴 유형이 올바르게 추론되고 모의 유형별 자동 배선이 예상대로 작동합니다. 결과적으로 MockitoFactoryBean, EasyMockFactoryBean 또는 Springockito와 같은 사용자 정의 해결 방법은 더 이상 필요하지 않습니다.

이것은 이것이 즉시 작동해야 함을 의미합니다.

<bean id="dao" class="org.mockito.Mockito" factory-method="mock">
    <constructor-arg value="com.package.Dao" />
</bean>

아래 코드는 자동 배선과 함께 작동합니다. 가장 짧은 버전은 아니지만 표준 스프링 / mockito 항아리에서만 작동해야 할 때 유용합니다.

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

당신이 사용하는 경우 봄> = 3.0 , 스프링스 사용해보십시오 @Configuration애플리케이션 컨텍스트의 일부를 정의하는 주석을

@Configuration
@ImportResource("com/blah/blurk/rest-of-config.xml")
public class DaoTestConfiguration {

    @Bean
    public ApplicationService applicationService() {
        return mock(ApplicationService.class);
    }

}

@ImportResource를 사용하지 않으려면 다른 방법으로도 수행 할 수 있습니다.

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

자세한 내용은 spring-framework-reference : Java 기반 컨테이너 구성을 살펴보십시오.


아마도 완벽한 솔루션은 아니지만 단위 테스트를 위해 DI를 수행하기 위해 스프링을 사용하지 않는 경향이 있습니다. 단일 빈 (테스트중인 클래스)에 대한 종속성은 일반적으로 지나치게 복잡하지 않으므로 테스트 코드에서 직접 주입을 수행합니다.


Mockito를 사용하여 다음을 수행 할 수 있습니다.

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

위의 접근 방식을 기반으로 몇 가지 예 게시

봄 :

@ContextConfiguration(locations = { "classpath:context.xml" })
@RunWith(SpringJUnit4ClassRunner.class)
public class TestServiceTest {
    @InjectMocks
    private TestService testService;
    @Mock
    private TestService2 testService2;
}

봄없이 :

@RunWith(MockitoJUnitRunner.class)
public class TestServiceTest {
    @InjectMocks
    private TestService testService = new TestServiceImpl();
    @Mock
    private TestService2 testService2;
}

업데이트 -새로운 답변은 https://stackoverflow.com/a/19454282/411229 입니다. 이 답변은 3.2 이전 스프링 버전의 답변에만 적용됩니다.

나는 이것에 대한보다 확실한 해결책을 찾기 위해 잠시 동안 보았다. 이 블로그 게시물은 모든 요구 사항을 다루는 것으로 보이며 Bean 선언의 순서에 의존하지 않습니다. Mattias Severson의 모든 크레딧. http://www.jayway.com/2011/11/30/spring-integration-tests-part-i-creating-mock-objects/

기본적으로 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;
    }
}

다음으로 스프링 설정을 다음과 같이 업데이트하십시오.

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

Springockito의 개발 속도공개 문제 수를 살펴보면 요즘 테스트 스위트 스택에 도입하는 것이 조금 걱정됩니다. Spring 4 릴리스 이전에 마지막 릴리스가 완료된 사실은 "Spring 4와 쉽게 통합 할 수 있습니까?"와 같은 질문을합니다. 나는 그것을 시도하지 않았기 때문에 모른다. 통합 테스트에서 Spring bean을 조롱 해야하는 경우 순수한 Spring 접근 방식을 선호합니다.

일반 스프링 기능만으로 스프링 빈을 가짜로 만드는 옵션이 있습니다. 당신은 사용할 필요가 @Primary, @Profile그리고 @ActiveProfiles그것에 대한 주석. 주제에 대한 블로그 게시물을 작성했습니다.


모의를 제공하는 MockFactory를 생성하기 위해 teabot과 비슷한 대답을 찾았습니다. 나는 (narkisr에 대한 링크가 죽은 이후) 모의 공장을 만들려면 다음 예를 사용 : 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>

이것은 또한 Spring이 조롱 된 콩에서 주입을 해결하려는 것을 방지하는 데 도움이됩니다.


<bean id="mockDaoFactory" name="dao" class="com.package.test.MocksFactory">
    <property name="type" value="com.package.Dao" />
</bean>

이 ^는 XML 파일에서 처음 / 초기 선언되면 완벽하게 작동합니다. 모키 토 1.9.0 / 봄 3.0.5


Markus T의 답변에 사용 된 접근 방식과 조롱 할 클래스를 지정할 수 ImportBeanDefinitionRegistrar있는 사용자 정의 주석 ( @MockedBeans) 을 찾는 간단한 도우미 구현을 조합하여 사용합니다 . 이 접근 방식을 사용하면 조롱과 관련된 상용구 코드를 사용하여 간결한 단위 테스트가 수행된다고 생각합니다.

다음은 이러한 접근 방식으로 샘플 단위 테스트를 보는 방법입니다.

@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
        }
    }
}

이를 위해서는 두 가지 간단한 도우미 클래스 인 사용자 지정 주석 ( @MockedBeans)과 사용자 지정 ImportBeanDefinitionRegistrar구현을 정의 해야합니다. @MockedBeans주석 정의의 요구로 주석 할 @Import(CustomImportBeanDefinitionRegistrar.class)과는 ImportBeanDefinitionRgistrar그것의의 구성에 조롱 콩 정의를 추가해야 registerBeanDefinitions하는 방법.

당신이 접근 방식을 좋아한다면 blogpost 에서 샘플 구현찾을 수 있습니다 .


Kresimir Nesek의 제안에 따라 솔루션을 개발했습니다. 코드를 좀 더 깨끗하고 모듈화하기 위해 @EnableMockedBean 새 주석을 추가했습니다 .

@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();
    }

}

나는 그것을 설명 하는 게시물 을 작성 했습니다.


프로젝트를 Spring Boot 1.4로 마이그레이션하는 것이 좋습니다. 그 후 새로운 주석 @MockBean사용 하여com.package.Dao


오늘 나는 Mockito bean 이전에 선언 한 스프링 컨텍스트가로드되지 않았다는 것을 알았습니다. 모의 후에 이동 한 후 앱 컨텍스트가 성공적으로로드되었습니다. 조심하세요 :)


기록을 위해 모든 테스트는 픽스처를 지연 초기화하여 다음과 같이 올바르게 작동합니다.

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

나는 이론적 근거가 Mattias가 여기 (포스트의 맨 아래)에서 설명하는 것이라고 생각합니다. 해결책은 콩이 선언 된 순서를 변경하고 있다고 가정합니다. 게으른 초기화는 끝 부분에 픽스처가 선언 된 "정렬"입니다.


Controller Injection을 사용하는 경우 지역 변수가 "최종"이 아닌지 확인하십시오

참고 URL : https://stackoverflow.com/questions/2457239/injecting-mockito-mocks-into-a-spring-bean

반응형