복붙노트

[SPRING] 스프링 MVC 컨트롤러 테스트에서 Service 클래스를 모의 수 없습니다

SPRING

스프링 MVC 컨트롤러 테스트에서 Service 클래스를 모의 수 없습니다

Spring 3.2 MVC 애플리케이션이 있고 Spring MVC 테스트 프레임 워크를 사용하여 컨트롤러의 동작에 대한 GET 및 POST 요청을 테스트하고있다. 나는 서비스를 조롱하기 위해 Mockito를 사용하고 있지만 mock이 무시되고 있으며 실제 서비스 계층이 사용되고 있음을 알게되었다. (결과적으로 데이터베이스가 손상되었다.)

내 컨트롤러 테스트 코드 :

package name.hines.steven.medical_claims_tracker.controllers;

import static org.mockito.Matchers.isA;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;
import name.hines.steven.medical_claims_tracker.domain.Policy;
import name.hines.steven.medical_claims_tracker.services.PolicyService;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration({ "classpath:/applicationContext.xml", "classpath:/tests_persistence-applicationContext.xml" })
public class PolicyControllerTest {

    @Mock
    PolicyService service;

    @Autowired
    private WebApplicationContext wac;

    private MockMvc mockMvc;

    @Before
    public void setup() {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();

        // this must be called for the @Mock annotations above to be processed.
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void createOrUpdateFailsWhenInvalidDataPostedAndSendsUserBackToForm() throws Exception {
        // Post no parameters in this request to force errors
        mockMvc.perform(post("/policies/persist")).andExpect(status().isOk())
            .andExpect(model().attributeHasErrors("policy"))
            .andExpect(view().name("createOrUpdatePolicy"));
    }

    @Test
    public void createOrUpdateSuccessful() throws Exception {

        // Mock the service method to force a known response
        when(service.save(isA(Policy.class))).thenReturn(new Policy());

        mockMvc.perform(
                post("/policies/persist").param("companyName", "Company Name")
                .param("name", "Name").param("effectiveDate", "2001-01-01"))
                .andExpect(status().isMovedTemporarily()).andExpect(model().hasNoErrors())
                .andExpect(redirectedUrl("list"));
    }
}

두 개의 컨텍스트 구성 파일이 있음을 알게 될 것입니다. 컨트롤러 테스트가 실제 서비스 레이어에 도달하는 것을 막을 수 없다면 서비스 레이어는 테스트 데이터베이스를 가리키는 리포지토리를 가질 수 있기 때문에 이것은 해킹입니다. 나는 더 이상이 해킹으로 도망 갈 수 없으며 내 서비스 계층을 제대로 조롱 할 수 있어야합니다.

왜 when (service.save (isA (Policy.class))). thenReturn (새 정책 ()); PolicyService에서 save 메소드를 실행 해보십시오. 어딘가에 mockito 구성이 누락 되었습니까? Spring 설정에 넣어야 할 것이 있습니까? 내 reasearch 지금까지 "봄 mvc 테스트 mockito 작동하지"인터넷 검색에 국한되어 있지만 그게 많이 계속하지 않았다.

감사.

당신은 바로 @ tom-verelst였습니다. PolicyService 서비스를 언급했습니다. MockMvc 내부의 서비스는 물론 Spring에 의해 주입 될 것이다.

나는 약간의 조사를했고 @InjectMocks가 사용되는 것을 설명하는 좋은 일을 한 블로그 게시물을 발견했다.

그런 다음 개인 MockMvc mockMvc에 @InjectMocks를 사용하여 주석을 달았지만 여전히 동일한 문제가 발생했습니다. 즉, MockMvc 내부의 서비스가 기대했던 것처럼 조롱되지 않았습니다. 나는 디버깅하는 동안 PointServiceImpl의 save 메소드가 호출되는 지점에서 스택 트레이스를 추가했다. (조롱 된 서비스의 save 메소드에 대한 원하는 호출과 반대이다.)

Thread [main] (Suspended (breakpoint at line 29 in DomainEntityServiceImpl) PolicyServiceImpl(DomainEntityServiceImpl<T>).save(T) line: 29

NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]
NativeMethodAccessorImpl.invoke(Object, Object[]) line: 39
DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 25
Method.invoke(Object, Object...) line: 597  
AopUtils.invokeJoinpointUsingReflection(Object, Method, Object[]) line: 317
ReflectiveMethodInvocation.invokeJoinpoint() line: 183  
ReflectiveMethodInvocation.proceed() line: 150  
TransactionInterceptor$1.proceedWithInvocation() line: 96
TransactionInterceptor(TransactionAspectSupport).invokeWithinTransaction(Method, Class, TransactionAspectSupport$InvocationCallback) line: 260  
TransactionInterceptor.invoke(MethodInvocation) line: 94
ReflectiveMethodInvocation.proceed() line: 172  
JdkDynamicAopProxy.invoke(Object, Method, Object[]) line: 204
$Proxy44.save(DomainEntity) line: not available 
PolicyController.createOrUpdate(Policy, BindingResult) line: 64
NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]
NativeMethodAccessorImpl.invoke(Object, Object[]) line: 39
DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 25
Method.invoke(Object, Object...) line: 597  
ServletInvocableHandlerMethod(InvocableHandlerMethod).invoke(Object...) line: 219
ServletInvocableHandlerMethod(InvocableHandlerMethod).invokeForRequest(NativeWebRequest, ModelAndViewContainer, Object...) line: 132    
ServletInvocableHandlerMethod.invokeAndHandle(ServletWebRequest, ModelAndViewContainer, Object...) line: 104    
RequestMappingHandlerAdapter.invokeHandleMethod(HttpServletRequest, HttpServletResponse, HandlerMethod) line: 746   
RequestMappingHandlerAdapter.handleInternal(HttpServletRequest, HttpServletResponse, HandlerMethod) line: 687   
RequestMappingHandlerAdapter(AbstractHandlerMethodAdapter).handle(HttpServletRequest, HttpServletResponse, Object) line: 80 
TestDispatcherServlet(DispatcherServlet).doDispatch(HttpServletRequest, HttpServletResponse) line: 925  
TestDispatcherServlet(DispatcherServlet).doService(HttpServletRequest, HttpServletResponse) line: 856   
TestDispatcherServlet(FrameworkServlet).processRequest(HttpServletRequest, HttpServletResponse) line: 915   
TestDispatcherServlet(FrameworkServlet).doPost(HttpServletRequest, HttpServletResponse) line: 822
TestDispatcherServlet(HttpServlet).service(HttpServletRequest, HttpServletResponse) line: 727
TestDispatcherServlet(FrameworkServlet).service(HttpServletRequest, HttpServletResponse) line: 796
TestDispatcherServlet.service(HttpServletRequest, HttpServletResponse) line: 66
TestDispatcherServlet(HttpServlet).service(ServletRequest, ServletResponse) line: 820
MockFilterChain$ServletFilterProxy.doFilter(ServletRequest, ServletResponse, FilterChain) line: 168
MockFilterChain.doFilter(ServletRequest, ServletResponse) line: 136
MockMvc.perform(RequestBuilder) line: 134   
PolicyControllerTest.createOrUpdateSuccessful() line: 67
NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]
NativeMethodAccessorImpl.invoke(Object, Object[]) line: 39
DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 25
Method.invoke(Object, Object...) line: 597  
FrameworkMethod$1.runReflectiveCall() line: 44  
FrameworkMethod$1(ReflectiveCallable).run() line: 15    
FrameworkMethod.invokeExplosively(Object, Object...) line: 41
InvokeMethod.evaluate() line: 20    
RunBefores.evaluate() line: 28  
RunBeforeTestMethodCallbacks.evaluate() line: 74    
RunAfterTestMethodCallbacks.evaluate() line: 83 
SpringRepeat.evaluate() line: 72    
SpringJUnit4ClassRunner.runChild(FrameworkMethod, RunNotifier) line: 231
SpringJUnit4ClassRunner.runChild(Object, RunNotifier) line: 88
ParentRunner$3.run() line: 193  
ParentRunner$1.schedule(Runnable) line: 52  
SpringJUnit4ClassRunner(ParentRunner<T>).runChildren(RunNotifier) line: 191
ParentRunner<T>.access$000(ParentRunner, RunNotifier) line: 42
ParentRunner$2.evaluate() line: 184 
RunBeforeTestClassCallbacks.evaluate() line: 61 
RunAfterTestClassCallbacks.evaluate() line: 71  
SpringJUnit4ClassRunner(ParentRunner<T>).run(RunNotifier) line: 236
SpringJUnit4ClassRunner.run(RunNotifier) line: 174  
JUnit4TestMethodReference(JUnit4TestReference).run(TestExecution) line: 50
TestExecution.run(ITestReference[]) line: 38    
RemoteTestRunner.runTests(String[], String, TestExecution) line: 467
RemoteTestRunner.runTests(TestExecution) line: 683  
RemoteTestRunner.run() line: 390    
RemoteTestRunner.main(String[]) line: 197   

더 많은 연구 (@Mock을 사용할 때 Nick 값을 Spring 빈에 삽입하는 Mockito)는 테스트 내에서 PolicyController 멤버 변수에 @InjectMocks를 적용 할 것을 제안했지만 첫 번째 링크의 답변 중 하나에서 지적했듯이 Spring 그것에 대해 아무 것도 모른다.

해결법

  1. ==============================

    1.@J Andy의 생각의 덕택으로, 나는 이것에 대한 잘못된 길로 향하고 있었다는 것을 깨달았다. 업데이트 1에서는 MockMvc에 모의 서비스를 주입하려고했지만 뒤로 물러나서 테스트 중이던 MockMvc가 아니라는 것을 깨달았습니다. 테스트하려는 PolicyController였습니다.

    @J Andy의 생각의 덕택으로, 나는 이것에 대한 잘못된 길로 향하고 있었다는 것을 깨달았다. 업데이트 1에서는 MockMvc에 모의 서비스를 주입하려고했지만 뒤로 물러나서 테스트 중이던 MockMvc가 아니라는 것을 깨달았습니다. 테스트하려는 PolicyController였습니다.

    약간의 배경 지식을 제공하기 위해 스프링 자체 내에서 컨트롤러를 실행하여 제공되는 것 (예 : RESTful 컨트롤러 액션 호출)을 테스트하기 위해 스프링 MVC 애플리케이션에서 @Controller의 전통적인 단위 테스트를 피하려고했다. Spring 내에서 테스트를 수행 할 수있게 해주는 Spring MVC Test 프레임 워크를 사용하면이 작업을 수행 할 수 있습니다.

    제 초기 질문의 코드에서 WebApplicationContext에서 스프링 MVC 테스트를 실행하고있는 것을 볼 수 있습니다. (즉 this.mockMvc = MockMvcBuilders.webAppContextSetup (this.wac) .build (); ) 내가하는 일은 독립 실행 형이었습니다. 독립 실행 형을 사용하면 테스트 할 컨트롤러를 직접 삽입 할 수 있으므로 서비스가 컨트롤러에 주입되는 방식 (즉 모의 서비스 사용 강제)을 제어 할 수 있습니다.

    이것은 코드에서 더 쉽게 설명됩니다. 그래서 다음 컨트롤러 :

    import javax.validation.Valid;
    
    import name.hines.steven.medical_claims_tracker.domain.Benefit;
    import name.hines.steven.medical_claims_tracker.domain.Policy;
    import name.hines.steven.medical_claims_tracker.services.DomainEntityService;
    import name.hines.steven.medical_claims_tracker.services.PolicyService;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    import org.springframework.validation.BindingResult;
    import org.springframework.web.bind.annotation.ModelAttribute;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.servlet.ModelAndView;
    
    @Controller
    @RequestMapping("/policies")
    public class PolicyController extends DomainEntityController<Policy> {
    
        @Autowired
        private PolicyService service;
    
        @RequestMapping(value = "persist", method = RequestMethod.POST)
        public String createOrUpdate(@Valid @ModelAttribute("policy") Policy policy, BindingResult result) {
            if (result.hasErrors()) {
                return "createOrUpdatePolicyForm";
            }
            service.save(policy);
            return "redirect:list";
        }
    }
    

    이제 서비스가 성공적으로 조롱되고 테스트 데이터베이스가 더 이상 작동하지 않는 다음 테스트 클래스가 있습니다.

    package name.hines.steven.medical_claims_tracker.controllers;
    
    import static org.mockito.Matchers.isA;
    import static org.mockito.Mockito.when;
    import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
    import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
    import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
    import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
    import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;
    import name.hines.steven.medical_claims_tracker.domain.Policy;
    import name.hines.steven.medical_claims_tracker.services.PolicyService;
    
    import org.junit.Before;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.mockito.InjectMocks;
    import org.mockito.Mock;
    import org.mockito.MockitoAnnotations;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    import org.springframework.test.web.servlet.MockMvc;
    import org.springframework.test.web.servlet.setup.MockMvcBuilders;
    
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration({ "classpath:/applicationContext.xml" })
    public class PolicyControllerTest {
    
        @Mock
        PolicyService policyService;
    
        @InjectMocks
        PolicyController controllerUnderTest;
    
        private MockMvc mockMvc;
    
        @Before
        public void setup() {
    
            // this must be called for the @Mock annotations above to be processed
            // and for the mock service to be injected into the controller under
            // test.
            MockitoAnnotations.initMocks(this);
    
            this.mockMvc = MockMvcBuilders.standaloneSetup(controllerUnderTest).build();
    
        }
    
        @Test
        public void createOrUpdateFailsWhenInvalidDataPostedAndSendsUserBackToForm() throws Exception {
            // POST no data to the form (i.e. an invalid POST)
            mockMvc.perform(post("/policies/persist")).andExpect(status().isOk())
            .andExpect(model().attributeHasErrors("policy"))
            .andExpect(view().name("createOrUpdatePolicy"));
        }
    
        @Test
        public void createOrUpdateSuccessful() throws Exception {
    
            when(policyService.save(isA(Policy.class))).thenReturn(new Policy());
    
            mockMvc.perform(
                    post("/policies/persist").param("companyName", "Company Name")
                    .param("name", "Name").param("effectiveDate", "2001-01-01"))
                    .andExpect(status().isMovedTemporarily()).andExpect(model().hasNoErrors())
                    .andExpect(redirectedUrl("list"));
        }
    }
    

    나는 봄에 관해서도 여전히 많은 것을 배우고 있기 때문에 나의 설명을 향상시킬 어떤 의견이라도 환영받을 것이다. 이 블로그 게시물은 나에게이 솔루션을 제안하는 데 도움이되었습니다.

  2. ==============================

    2.이 섹션, 11.3.6 Spring MVC 테스트 프레임 워크, Spring document 11. 테스트는 그것에 대해 이야기하고 있지만, 그것은 분명하지 않다.

    이 섹션, 11.3.6 Spring MVC 테스트 프레임 워크, Spring document 11. 테스트는 그것에 대해 이야기하고 있지만, 그것은 분명하지 않다.

    설명을 위해 문서의 예제를 계속합시다. 샘플 테스트 클래스는 다음과 같습니다.

    @RunWith(SpringJUnit4ClassRunner.class)
    @WebAppConfiguration
    @ContextConfiguration("test-servlet-context.xml")
    public class AccountTests {
    
        @Autowired
        private WebApplicationContext wac;
    
        private MockMvc mockMvc;
    
        @Autowired
        private AccountService accountService;
    
        // ...
    
    }
    

    org.example.AppController가 컨트롤러로 있다고 가정하십시오. test-servlet-context.xml 파일에서

    <bean class="org.example.AppController">
        <property name="accountService" ref="accountService" />
    </bean>
    
    <bean id="accountService" class="org.mockito.Mockito" factory-method="mock">
        <constructor-arg value="org.example.AccountService"/>
    </bean>
    

    문서에 컨트롤러의 배선 부분이 없습니다. 그리고 필드 주입을 사용하는 경우 accountService에 대한 setter 주입으로 변경해야합니다. 또한 constructor-arg의 값 (org.example.AccountService)은 클래스가 아니라 인터페이스임을 유의하십시오.

    AccountTests의 설정 방법에서

    @Before
    public void setup() {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
    
        // You may stub with return values here
        when(accountService.findById(1)).thenReturn(...);
    }
    

    테스트 방법은 다음과 같이 보일 수 있습니다.

    @Test
    public void testAccountId(){
        this.mockMvc.perform(...)
        .andDo(print())
        .andExpect(...);  
    }
    

    andDo (print ())는 편리합니다. "import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;"를 수행하십시오.

  3. ==============================

    3.이것은 아마도 Spring과 Mockito가 빈을 주입하려고 시도 할 때 발생하는 문제 일 것입니다. 이러한 문제를 피할 수있는 한 가지 방법은 Spring ReflectionTestUtils를 사용하여 수동으로 서비스 모의를 삽입하는 것입니다.

    이것은 아마도 Spring과 Mockito가 빈을 주입하려고 시도 할 때 발생하는 문제 일 것입니다. 이러한 문제를 피할 수있는 한 가지 방법은 Spring ReflectionTestUtils를 사용하여 수동으로 서비스 모의를 삽입하는 것입니다.

    이 경우 setup () 메소드는 다음과 같이 보입니다.

    @Before
    public void setup() {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
    
        // this must be called for the @Mock annotations above to be processed.
        MockitoAnnotations.initMocks(this);
    
        // TODO: Make sure to set the field name in UUT correctly
        ReflectionTestUtils.setField( mockMvc, "service", service );
    }
    

    추신 귀하의 명명 규칙은 IMHO 비트 떨어져 있고 mockMvc 테스트하려는 클래스 (UUT) 가정합니다. 대신 다음 이름을 사용합니다.

    @Mock PolicyService mockPolicyService;
    @InjectMocks Mvc mvc;
    
  4. ==============================

    4.당신은 PolicyService를위한 모의 (mock)를 만들고 있습니다 만, 내가 말할 수있는 한 그것을 MockMvc에 주입하지는 않습니다. 즉, Spring 구성에 정의 된 PolicyService가 모의 객체 대신 호출됩니다.

    당신은 PolicyService를위한 모의 (mock)를 만들고 있습니다 만, 내가 말할 수있는 한 그것을 MockMvc에 주입하지는 않습니다. 즉, Spring 구성에 정의 된 PolicyService가 모의 객체 대신 호출됩니다.

    PolicyService 모의 작업을 MockMvc에 삽입하여 설정하거나 모의 주입을 위해 Springockito를 살펴보십시오.

  5. ==============================

    5.나는 Mockmvc의 독립 실행 형 서비스를 선호합니다.

    나는 Mockmvc의 독립 실행 형 서비스를 선호합니다.

    나를 위해 언급 된 일

    public class AccessControllerTest {
    
        private MockMvc mockMvc;
    
        @Mock
        private AccessControlService accessControlService;
    
        @InjectMocks
        private AccessController accessController;
    
        @Before
        public void setup() {
            MockitoAnnotations.initMocks(this);
            this.mockMvc =  MockMvcBuilders.standaloneSetup(accessController).build();
        }
    
        @Test
        public void validAccessControlRequest() throws Exception {
            Bundle bundle = new Bundle();
            bundle.setAuthorized(false);
            Mockito.when(accessControlService.retrievePatient(any(String.class)))
             .thenReturn(bundle);
    
            mockMvc.perform(get("/access/user?user=3")).andExpect(status().isOk());
    }
    
  6. from https://stackoverflow.com/questions/16170572/unable-to-mock-service-class-in-spring-mvc-controller-tests by cc-by-sa and MIT license