0%

SpringBoot2.x(四)单元测试进阶实战和自定义异常处理

单元测试实战

引入SpringBoot测试依赖:

1
2
3
4
5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

@SpringBootTest&@RunWith

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package top.zhenganwen.springbootweb;

import junit.framework.TestCase;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class) //加载spring容器
@SpringBootTest(classes = {SpringbootWebApplication.class}) //指定启动类
public class Test1 {

@Before
public void before() {
System.out.println("before test");
}

@Test
public void testHello() {
System.out.println("test hello");
TestCase.assertEquals(1, 1);
}

@After
public void after() {
System.out.println("after test");
}
}

TestCaseAssert中封装了一些引入断言的方法,大家按喜好调用。


测试进阶之MockMvc

MockMvcorg.springframework.test.web.servlet包下一个用来测试服务端接口的工具类,官方描述如下:

Main entry point for server-side Spring MVC test support

Get Started

  • Controller
1
2
3
4
5
6
7
8
9
10
@RestController
public class HelloWorld {

private Map params = new HashMap<>();

@RequestMapping("/hello")
public String hello() {
return "Hello World";
}
}
  • ControllerTest
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package top.zhenganwen.springbootweb;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = {SpringbootWebApplication.class})
@AutoConfigureMockMvc
public class HelloWorldTest {

@Autowired
private MockMvc mockMvc;

@Test
public void testGET() throws Exception {
MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get("/hello")).andExpect(MockMvcResultMatchers.status().isOk()).andReturn();
MockHttpServletResponse response = mvcResult.getResponse();
System.out.println(response.getStatus()); //200
System.out.println(response.getContentAsString()); //Hello World
}

}

如何获取MockMvc实例

在使用MockMvc之前,你需要在你的测试类上添加 @AutoConfigureMockMvc注解,之后便可将其作为类成员变量并自动注入。

movkMvc.perform(RequestBuilder rb)

获取 MockMvc实例之后,你就可以用它构建http请求测试服务端接口了。你需要调用它的 perform()方法,并传入一个 RequestBuilder接口类型的实例。该接口做的事就是构建http请求,而 perform()则将构建的请求发送出去并获取响应结果。

查看 RequestBuilder接口注释可知可调用静态工厂 MockMvcRequestBuilders中的方法获取一个 MockHttpServletRequest实例。查看 MockMvcRequestBuilders,发现其中对应http提交方式提供了 getpostdeleteput等方法,均返回MockHttpServletRequest实例。

.andExpect()&MockMvcResultMatchers

这是一种链式调用。andExpect()封装了一层断言,你可以在参数中断言请求的响应结果,如 andExpect(MockMvcResultMatchers.status().isOk())就是断言响应的状态码为 200。你需要配合使用 MockMvcResultMatchers,其中提供了许多用来断言响应结果的方法。

你可以通过这种链式调用在断言成功时才获取响应结果以提高性能 :.andExpect().andReturn()

.andReturn()&MvcResult

你可以通过 perform().andReturn()获取 MvcResult,在通过 mvcResult.getResponse()获取响应结果 MockHttpServletResponse,其中封装了状态码、头信息、响应体等。


个性化启动banner&设置debug日志

自定义banner

自定义banner内容

如在 classpath下创建 banner.txt内容如下:

1
2
3
==================
www.zhenganwen.top
==================SpringBoot2.x>>>>>>>

指定加载自定义banner

你需要在 application.properties 中指定自定义banner的文件,从而替换springboot启动时默认输出的banner

1
spring.banner.location=banner.txt

启动应用时获取更过日志信息

启动springboot时,默认会在控制台输出 INFO级别的信息,有时我们排错需要更详细的信息,这时我们可以在运行 jar包时指定参数 --debug以获取 DEBUG级别的日志:

1
java -jar springboot-web-0.0.1-SNAPSHOT.jar --debug

配置全局异常实战

当我们的服务端接口发生异常时,返回给前端的是一个不友好的响应结果。

以最常见的 1/0为例:

1
2
3
4
5
@RequestMapping("testEx")
public Object testEx() {
int i = 1 / 0;
return results;
}

响应结果:

1
2
3
Thu Jul 19 10:11:13 CST 2018
There was an unexpected error (type=Internal Server Error, status=500).
/ by zero

这是springboot默认提供的服务端异常时响应的结果页。

创建全局异常处理类(组件)

你只需要在异常处理类上添加 @ControllerAdvice@RestControllerAdvice注解并在异常处理方法上声明要捕获的异常即可 @ExceptionHandler(value=xxxException.class)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package top.zhenganwen.springbootweb.advice;

import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

@RestControllerAdvice
public class GlobalExceptionAdvice {

//全局异常处理,捕获所有Exception及其子类异常
@ExceptionHandler(value = Exception.class)
public Object handleException(Exception e, HttpServletRequest request) {
Map result = new HashMap();
//用100表示服务器接口异常
result.put("code", 100);
//返回错误信息
result.put("msg", e.getMessage());
//返回异常接口
result.put("url", request.getRequestURL());
return result;
}
}

实际上就是采用AOP编程对处理请求的Controller的映射方法的一个增强,捕获对应的异常(@ExceptionHandler)。Contorller监听http请求,而 ControllerAdvice则监听 Controller处理请求过程中抛出的异常,但最终只会返回一个响应结果。异常捕获级别在 value = Exception.class中指定。

响应结果:

1
{"msg":"/ by zero","code":100,"url":"http://localhost:8080/testEx"}

Logger

你可以通过Logger输出或保存错误日志

1
2
3
4
5
6
private static final Logger LOG = LoggerFactory.getLogger(GlobalExceptionAdvice.class);
@ExceptionHandler(value = Exception.class)
public Object handleException(Exception e, HttpServletRequest request) {
LOG.error("url,msg",request.getRequestURL()+","+e.getMessage());
..
}

自定义异常和错误页面

自定义异常

需要实现一个异常类:

1
2
3
4
5
6
7
@Data
@AllArgsConstructor
@NoArgsConstructor
public class MyException extends RuntimeException{
private int code;
private String msg;
}

捕获自定义异常:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@RestControllerAdvice
public class ControllerExceptionAdvice {

private static final Logger LOG = LoggerFactory.getLogger(GlobalExceptionAdvice.class);

@ExceptionHandler(value = Exception.class)
public Object handleException(Exception e, HttpServletRequest request) {
LOG.error("url,msg",request.getRequestURL()+","+e.getMessage());
Map result = new HashMap();
result.put("code", 100);
result.put("msg", e.getMessage());
result.put("url", request.getRequestURL());
return result;
}

@ExceptionHandler(value = MyException.class)
public Object handleMyException() {
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("error.html");
return modelAndView;
}
}

测试

1
2
3
4
@RequestMapping("testEx")
public Object testEx() {
throw new MyException();
}

访问http://localhost:8080/testEx:

1
抱歉,网络异常,请稍后再试!
鼓励一下~