spring - test - when mockito




봄 콩으로 모키 토 모의 주입 (16)

JUnit으로 단위 테스트를하기 위해 Mockito 모의 객체를 Spring (3+) bean에 삽입하고 싶습니다. 내 bean 의존성은 private 멤버 필드에 @Autowired 주석을 사용하여 주입된다.

ReflectionTestUtils.setField 사용하는 것을 고려해 봤지만 내가 주입하고자하는 빈 인스턴스는 실제로 프록시이므로 대상 클래스의 private 멤버 필드를 선언하지 않습니다. 의존성에 대한 공적인 세터를 만들고 싶지는 않습니다. 테스트 목적으로 인터페이스를 수정하는 것입니다.

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

<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 값을 설정하면 응용 프로그램 컨텍스트를 시작할 때 오류가 발생하지 않습니다.


Spring 3.2부터는 더 이상 문제가되지 않습니다. Spring은 이제 일반 팩토리 메소드의 결과에 대한 Autowiring을 지원합니다. 이 블로그 게시물의 "Generic Factory Methods"섹션을 참조하십시오 : spring.io/blog/2012/11/07/… .

요점은 :

Spring 3.2에서는 팩토리 메소드의 일반적인 리턴 타입이 적절하게 유추되고 모의 객체의 타입에 의한 자동 와이어 링이 예상대로 작동한다. 결과적으로 MockitoFactoryBean, EasyMockFactoryBean 또는 Springockito와 같은 사용자 지정 작업이 더 이상 필요하지 않게됩니다.

즉, 상자에서 꺼내야합니다.

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

Spring Boot 1.4를 사용한다면,이 방법을 사용하는 것이 좋습니다. 클래스의 새 브랜드 @SpringBootTest 와 필드의 @MockBean 을 사용하면 Spring Boot가이 유형의 모의 객체를 생성하고 @MockBean 를 삽입하는 대신 컨텍스트에 주입합니다.

@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를 사용하지 않거나 이전 버전을 사용하고 있다면, 더 많은 작업을해야합니다 :

스프링 컨텍스트에 mock을 삽입하는 @Configuration 빈을 생성한다 :

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

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

@Primary 주석을 사용하면 한정자가 지정되지 않은 경우이 bean이 우선 순위를 갖는다는 것을 Spring에 알려줍니다.

mock을 사용할 클래스와 실제 bean을 사용할 클래스를 제어하려면 클래스에 @Profile("useMocks") 주석을 @Profile("useMocks") 합니다.

마지막으로, 테스트에서 userMocks 프로필을 활성화 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() {
        ....
    }
}

모의 객체를 사용하지 않고 실제 빈을 사용하려면 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() {
        ....
    }
}

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

<bean id="dao" class="org.mockito.Mockito" factory-method="mock"> 
    <constructor-arg value="com.package.Dao" /> 
</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가 포스트의 하단에서 설명하는 이유는 빈이 선언 된 순서를 변경하는 것입니다. 즉, 지연 초기화는 결국 선언 된 fixture를 갖는 "일종의"것입니다.


나는 Mockito를 사용하여 다음을 할 수있다.

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

나는 모의 답변을 제공하는 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으로부터의 injection을 해결하기를 원하지 않도록 도와줍니다.


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


오늘 나는 Mockito 콩 이전에 선언 한 봄의 상황이로드에 실패했음을 알게되었습니다. mock을 이동 한 후 앱 컨텍스트가 성공적으로로드되었습니다. 잘 보살펴주세요 :)


저는 Markus T가 응답으로 사용한 접근법과 ImportBeanDefinitionRegistrar 의 간단한 도우미 구현을 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 는 조롱 된 bean 정의를 registerBeanDefinitions 메소드의 구성에 추가해야합니다.

접근 방식이 마음에 들면 내 blogpost 에서 샘플 implementations 을 찾을 수 있습니다.


주어진:

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

    // etc
}

autowiring을 통해 테스트중인 클래스를로드하고, 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 가 기본적으로 수행하는 기능 임)


프로젝트를 스프링 부트 1.4로 마이그레이션하는 것이 좋습니다. 그런 다음 새로운 주석 @MockBean 을 사용하여 com.package.Dao 를 가짜로 만들 수 com.package.Dao


1.8.3 Mockito에는 @InjectMocks가 있기 때문에 이것은 매우 유용합니다. 내 JUnit 테스트는 @Run과 함께 MockitoJUnitRunner이며, 테스트 대상 클래스의 모든 종속성을 충족하는 @Mock 객체를 작성합니다.이 객체는 모두 private 멤버가 @InjectMocks로 주석을 추가 할 때 주입됩니다.

통합 테스트를 위해 @RunWith SpringJUnit4Runner 만 사용하십시오.

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


Spring> = 3.0을 사용하는 경우 Springs @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>

자세한 내용은 스프링 프레임 워크 참조 : Java 기반 컨테이너 구성을 참조하십시오.


업데이트 : 이 문제에 대한 더 나은, 더 깨끗한 솔루션이 있습니다. 먼저 다른 답변을 고려하십시오.

나는 결국 그의 블로그에서 ronen에 의해 이것에 대한 답을 발견했다. 문제는 Object 의 반환 유형을 선언하는 Mockito.mock(Class c) 메서드 Mockito.mock(Class c) 입니다. 결과적으로 Spring은 factory 메소드 리턴 타입에서 bean 타입을 추론 할 수 없다.

Ronen의 해결책 은 모의 객체를 반환하는 FactoryBean 구현을 만드는 것입니다. FactoryBean 인터페이스는 Factory Bean에 의해 생성 된 객체의 타입을 Spring이 질의 할 수있게합니다.

내 조롱 된 bean 정의는 다음과 같이 보입니다.

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

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

XML 파일에서 처음 / 일찍 선언하면 this ^가 완벽하게 작동합니다. Mockito 1.9.0 / 봄 3.0.5


@InjectMocks
private MyTestObject testObject;

@Mock
private MyDependentObject mockedObject;

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

그러면 시험 대상 클래스에 조롱 된 객체가 삽입됩니다. 이 경우 mockedObject가 testObject에 삽입 될 것이다. 이것은 위에서 언급했지만 여기에 코드가 있습니다.







mockito