【问题标题】:Unit testing code in catch block of a Spring ControllerSpring Controller 的 catch 块中的单元测试代码
【发布时间】:2017-03-25 17:26:51
【问题描述】:

我有以下结构的控制器:

@RequestMapping(value = "${foo.controller.requestMappingUrl.login}", method = RequestMethod.POST)
    public ResponseMessage<String> loginUser(
            @RequestParam("username") String username, HttpServletRequest httpServletRequest,
            HttpServletResponse httpServletResponse) {

    try {

       return fooService.login(username); // can mock test

    } catch (UserNotFoundException e) {
            //CANNOT MOCK TEST THIS BLOCK
            String errorMsg = LoggerUtil.printStackTrace(e);
            LOG.error("[RequestId: {}] UserNotFoundException: {}, Stack: {}", requestId, e.getMessage(), errorMsg);
            httpServletResponse.setStatus(HttpStatus.NOT_ACCEPTABLE.value());
            statusCode = StatusCode.UserNotFound.value();
            responseMessage.buildResponseMessage(StringUtils.EMPTY, HttpStatus.NOT_ACCEPTABLE, statusCode,
                    messageByLocaleService.getMessageResponse(statusCode, null, locale));
    }
}

当我模拟抛出异常 UserNotFoundException 时,我只得到 NestedServletException。即使我尝试添加预期 = NestedServletException.class。 corbetura 报告表明该代码块未包含在测试中。您有什么建议可以帮助测试 catch 块内的代码。

要求的测试代码:

@SuppressWarnings("unchecked")
@Test(expected = UserNotFoundException.class)
    public void testControllerUserNotFoundException() throws Exception {
        Response resp = new Response();
        resp.setStatusCode(StatusCode.UserNotFoundErrorCode);
        when(fooService.login(any(String.class)).thenThrow(UserNotFoundException.class);

        mockMvc.perform(post("/service-user/1.0/auth/login?&username=test")
                        .contentType(contentType)).andExpect(status().isNotAcceptable())
                .andExpect(jsonPath("$.statusCode", is("ERRORCODE144")));

    }

还有堆栈跟踪

java.lang.Exception: 意外异常, 预期的 但在 org.junit.internal.runners.statements.ExpectException.evaluate(ExpectException.java:28) 在 org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26) 在 org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75) 在 org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86) 在 org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84) 在 org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) 在 org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:252) 在 org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94) 在 org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) 在 org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) 在 org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) 在 org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) 在 org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) 在 org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) 在 org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70) 在 org.junit.runners.ParentRunner.run(ParentRunner.java:363) 在 org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191) 在 org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50) 在 org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38) 在 org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467) 在 org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683) 在 org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390) 在 org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197) 引起:org.springframework.web.util.NestedServletException: 请求处理失败;嵌套异常是 com.atlassian.crowd.exception.runtime.UserNotFoundException 在 org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:982) 在 org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:872) 在 javax.servlet.http.HttpServlet.service(HttpServlet.java:648) 在 org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846) 在 org.springframework.test.web.servlet.TestDispatcherServlet.service(TestDispatcherServlet.java:65) 在 javax.servlet.http.HttpServlet.service(HttpServlet.java:729) 在 org.springframework.mock.web.MockFilterChain$ServletFilterProxy.doFilter(MockFilterChain.java:167) 在 org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:134) 在 org.springframework.test.web.servlet.MockMvc.perform(MockMvc.java:155) 在 id.co.allianz.microservice.cop.app.auth.controller.AuthControllerTest.testControllerUserNotFoundException(AuthControllerTest.java:105) 在 sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 在 sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) 在 sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 在 java.lang.reflect.Method.invoke(Method.java:498) 在 org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) 在 org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) 在 org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) 在 org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) 在 org.junit.internal.runners.statements.ExpectException.evaluate(ExpectException.java:19) ... 22 更多原因: com.atlassian.crowd.exception.runtime.UserNotFoundException

【问题讨论】:

  • 可以发fooService.login的内容吗?
  • 对不起,cmmiiw 但是为什么你需要服务层代码,因为无论如何我正在模拟该层。服务层函数使用 Atlassian crowd 并抛出从 crowd api 返回的 UserNotFoundException。
  • 请贴出你得到的NestedServletException的测试代码和stacktrace。
  • @nanospeck 然后发布您模拟此功能的代码
  • 用模拟和堆栈跟踪更新了问题

标签: java spring unit-testing junit mockito


【解决方案1】:

您不能期望控制器方法中出现UserNotFoundException 异常,因为您只是通过简单地记录并返回带有ERRORCODE144 的响应来抑制它。

这里的最佳做法是配置ControllerAdvice,以便可以全局处理所有异常并且您的控制器看起来很干净,如下所示,我建议您也可以查看here关于spring控制器异常处理。

ExceptionControllerAdvice 类:

@ControllerAdvice
public class ExceptionControllerAdvice {

    @ExceptionHandler(UserNotFoundException.class)
    public ResponseEntity<String> handleUnexpectedException(UnexpectedException 
                     unExpectedExe) {

        //log and send the response back to the client here
    }
}

控制器方法:

@RequestMapping(value = "${foo.controller.requestMappingUrl.login}",
                                 method = RequestMethod.POST)
public ResponseMessage<String> loginUser(
            @RequestParam("username") String username,
               HttpServletRequest httpServletRequest,
               HttpServletResponse httpServletResponse) {

       return fooService.login(username);
}

JUnit 设置和测试方法:

@Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
    mockMvc = MockMvcBuilders.standaloneSetup(projectController)
                .setMessageConverters(new MappingJackson2HttpMessageConverter())
                .setControllerAdvice(new ExceptionControllerAdvice()).build();
}


@SuppressWarnings("unchecked")
@Test
    public void testControllerUserNotFoundException() throws Exception {
        Response resp = new Response();
        resp.setStatusCode(StatusCode.UserNotFoundErrorCode);
        when(fooService.login(any(String.class)).
        thenThrow(UserNotFoundException.class);
        mockMvc.perform(post("/service-user/1.0/auth/login?&username=test")
                        .contentType(contentType)).
     andExpect(status().isNotAcceptable())
                .andExpect(jsonPath("$.statusCode", is("ERRORCODE144")));
}

【讨论】:

  • 嘿,听起来是个不错的选择。但是如何处理编译器警告,在 Controller 类中使用 try/catch 块包围服务方法。
  • 不要在控制器内部catch它,而是在控制器方法签名中添加throws子句,以便ControllerAdvice会小心
  • 糟糕!我的错,我在我的测试类中用重复的名称嘲笑错误的异常,正在调用'import com.atlassian.crowd.exception.runtime.UserNotFoundException'而不是'import com.atlassian.crowd.exception.UserNotFoundException'。接受这个答案是因为我发现这种优雅的实现方式可以通过帮助及早识别它来帮助避免我的错误。我在这里写了一个简单的示例:github.com/nanospeck/BackbaseApp/blob/master/src/main/java/com/…
猜你喜欢
  • 2021-08-09
  • 1970-01-01
  • 2023-04-01
  • 1970-01-01
  • 2018-11-30
  • 2015-04-07
  • 2019-08-06
  • 2020-06-08
  • 1970-01-01
相关资源
最近更新 更多