it-source

Spring MVC 컨트롤러 테스트에서 서비스 클래스를 모의 실행할 수 없습니다.

criticalcode 2023. 7. 30. 17:53
반응형

Spring MVC 컨트롤러 테스트에서 서비스 클래스를 모의 실행할 수 없습니다.

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

컨트롤러 테스트의 코드:

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(new Policy());정책 서비스의 저장 방법을 시작하고 조롱하지 않습니까?어딘가에 모키토 구성이 누락되었나요?스프링 구성에 넣어야 할 것이 있습니까?지금까지 제 연구는 구글링 "봄 mvc 테스트 mockito가 작동하지 않음"으로 제한되었지만, 그것은 저에게 많은 것을 주지 않았습니다.

감사해요.


업데이트 1

요, 는 @tom-verelst, @tom-verelst, 는을것있다니었습언고급하저요그어당신았이옳▁▁to▁you다▁wastom니있▁@.PolicyService service;에 줄을 내테트사용내서비스부여하인라스을서 안에 .MockMvc물론 스프링이 윌을 주입했습니다.

저는 약간의 조사를 했고 무엇을 설명하는 을 잘 해주는 블로그 게시물을 발견했습니다.@InjectMocks에 사용됩니다.

저는 그런다주달다니았습을석음ating에 주석을 달아 .private MockMvc mockMvc와 함께@InjectMocks그리고 여전히 동일한 문제(즉, 내부 서비스)가 발생했습니다.MockMvc제가 예상했던 것처럼 조롱당하지 않았습니다.)디버깅하는 동안 스택 추적을 추가했습니다. 여기서 저장 방법은PolicyServiceImpl호출됩니다(모의된 서비스에서 저장 메서드에 대한 원하는 호출과 반대).

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   

추가 연구(Mockito @Mock을 사용할 때 Spring bean에 Null 값을 주입하는 Mockito?)는 다음을 적용할 것을 제안했습니다.@InjectMocksPolicyController멤버 변수는 테스트 내에 있지만 첫 번째 링크의 답변 중 하나에서 지적했듯이 스프링이 아무것도 모르기 때문에 이것은 아무것도 하지 않습니다.

@J Andy의 사고 방식 덕분에, 저는 제가 이것에 대해 잘못된 길을 가고 있다는 것을 깨달았습니다.를 업이에 1서서모주했입습다니고려하에 .MockMvc하지만 한 걸음 물러난 후에 나는 그것이 아니라는 것을 깨달았습니다.MockMvc그것은 테스트 중이었고, 그것은.PolicyController시험해보고 싶었습니다.

약간의 배경 정보를 제공하기 위해 Spring MVC 애플리케이션에서 @Controllers의 기존 장치 테스트는 피하고 싶었습니다. Spring 자체 내에서 컨트롤러를 실행함으로써만 제공되는 것을 테스트하고 싶었기 때문입니다(예: 컨트롤러 작업에 대한 RESTful 호출).Spring MVC Test 프레임워크를 사용하면 Spring 내에서 테스트를 실행할 수 있습니다.

첫 가 Spring 를 제첫번 MVC 테있실 MVC 스었것에서 실행하고 알 수 입니다.WebApplicationContext (계속,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"));
    }
}

저는 봄에 관해서는 아직도 많이 배우고 있기 때문에 제 설명을 개선할 수 있는 어떤 의견도 환영할 것입니다.이 블로그 게시물은 이 해결책을 생각하는 데 도움이 되었습니다.

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

SpringBoot에서 Springs @MockBean을 사용하는 경우 - 부두에서 말하는 것처럼:

컨텍스트에 정의된 동일한 유형의 기존 단일 빈이 모조로 대체됩니다.기존 빈이 정의되지 않은 경우 새 빈이 추가됩니다.

 @RunWith(SpringRunner.class)
 public class ExampleTests {

     @MockBean
     private ExampleService service;

이 섹션, 11.3.6 스프링 MVC 테스트 프레임워크, 스프링 문서 11. 테스트는 그것에 대해 이야기하지만, 어떤 면에서는 명확하지 않습니다.

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

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration("test-servlet-context.xml")
public class AccountTests {

    @Autowired
    private WebApplicationContext wac;

    private MockMvc mockMvc;

    @Autowired
    private AccountService accountService;

    // ...

}

조직 예제가 있다고 가정합니다.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 injection을 변경해야 합니다.또한 값(예: 예)에 유의하십시오.생성자-arg에 대한 AccountService)는 클래스가 아닌 인터페이스입니다.

계정의 설정 방법테스트, 당신은 할 것입니다.

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

Do(print()는 "static org.springframework.test.web.servlet.result를 가져옵니다.MockMvcResultHandlers.print;"입니다.

@WebMvcTest를 사용하는 최신 봄 릴리스의 다른 솔루션이 있습니다.아래의 예.

@RunWith(SpringRunner.class)
@WebMvcTest(CategoryAPI.class)
public class CategoryAPITest {

@Autowired
private MockMvc mvc;

@MockBean
CategoryAPIService categoryAPIService;

@SpyBean
Utility utility;

PcmResponseBean responseBean;

@Before
public void before() {
    PcmResponseBean responseBean = new PcmResponseBean("123", "200", null, null);
    BDDMockito.given(categoryAPIService.saveCategory(anyString())).willReturn(responseBean);
}

@Test
public void saveCategoryTest() throws Exception {
    String category = "{}";
    mvc.perform(post("/api/category/").content(category).contentType(MediaType.APPLICATION_JSON))
            .andExpect(status().isOk()).andExpect(jsonPath("messageId", Matchers.is("123")))
            .andExpect(jsonPath("status", Matchers.is("200")));
  }

}

여기에서는 범주만 로드합니다.Spring rest 컨트롤러 클래스인 API 클래스와 나머지는 모두 mock입니다.스프링은 모키토 @Mock 및 @SpyBean과 유사한 @Mock Bean 및 @SpyBean과 같은 자체 주석 버전을 가지고 있습니다.

이것은 아마도 Spring과 Mockito가 둘 다 콩을 주입하려고 시도하는 것과 관련된 문제일 것입니다.이러한 문제를 피할 수 있는 한 가지 방법은 스프링 리플렉션을 사용하는 것입니다.Utils를 테스트하여 서비스 모의실험을 수동으로 주입합니다.

이 경우 설정() 방법은 다음과 같습니다.

@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가 당신이 테스트하려는 클래스라고 생각합니다.대신 다음 이름을 사용합니다.

@Mock PolicyService mockPolicyService;
@InjectMocks Mvc mvc;

다음을 위한 조롱거리를 만들고 있습니다.PolicyService하지만 당신은 그것을 당신의 몸에 주입하지 않습니다.MockMvc내가 아는 바로는이것은 다음을 의미합니다.PolicyServiceSpring 구성에 정의된 항목이 mock 대신 호출됩니다.

그들의 조롱을 주입하거나.PolicyService당신의 것MockMvc설정하거나 스프링코키토에서 모의 주사를 놓는 것을 살펴보세요.

대체 솔루션:

  1. WebApplicationContext를 사용할 때 스프링 컨테이너에서 종속성을 찾습니다.따라서 우리가 모의실험을 하려면 스프링 컨테이너에서 이 작업을 수행해야 합니다. 이것이 컨텍스트 구성 파일입니다.

    @Bean public FileResourceService fileResourceService() {FileResourceServiceImppl 서비스 = Mockito.mock(FileResourceServiceImppl.class); 서비스 반환; }

  2. 테스트 클래스에서 이 모의 주석을 사용하면 스프링 컨테이너에 대해 의미가 없습니다.그들은 그것에 대해 아무것도 모릅니다.

언급URL : https://stackoverflow.com/questions/16170572/unable-to-mock-service-class-in-spring-mvc-controller-tests

반응형