背景
Java的单元测试可以使用多个框架,其中比较流行的包括:
- JUnit:JUnit是Java单元测试最常用的框架,它提供了一套丰富的API,可以方便地编写测试用例和测试套件。JUnit 5是JUnit的最新版本,引入了许多新功能和改进。
- Mockito:Mockito是一个模拟框架,可以模拟对象的行为和状态,以便在单元测试中检查方法的调用和参数。它提供了一组强大的API,可以方便地创建模拟对象和验证方法调用。
- Spring Test:如果你正在使用Spring框架开发应用程序,那么可以使用Spring Test框架进行单元测试。Spring Test提供了一套完整的测试解决方案,可以方便地测试Spring应用程序的各种方面。
Spring Boot Test的主要特点包括:
- 快速创建测试环境:通过使用Spring Boot的自动化配置功能,可以快速创建测试环境,无需手动配置。
- 提供丰富的测试功能:Spring Boot Test提供了丰富的测试功能,包括注解支持、MockMvc、RestTemplate等,可以方便地进行单元测试和集成测试。
- 集成JUnit和Mockito:Spring Boot Test集成了JUnit和Mockito等测试框架和模拟框架,可以方便地编写测试用例和模拟对象。
- 简化测试配置:通过使用Spring Boot的自动化配置功能,可以简化测试配置,只需关注测试用例的编写。
Spring Boot Test 包含了这些库
- JUnit 5:包含兼容JUnit4,Java 应用程序单元测试的事实标准
- Spring Test 和 SpringBootTest:对Spring Boot应用程序的公共和集成测试支持
- AssertJ:流式断言库
- Hamcrest:匹配对象库
- Mockito:Java 模拟框架
- JSONassert:JSON 断言库
- JsonPath: JSON XPath
MockMvc概念
MockMvc是Spring Test模块的一部分,它允许我们对Spring MVC控制器进行单元测试,而无需启动完整的Spring应用上下文。
MockMvc可以模拟HTTP请求和响应,MockMvc就像是一个虚拟的Spring MVC,能够让我们快速测试控制器。
MockMVC的基本步骤
(1) mockMvc.perform执行一个请求。
(2) MockMvcRequestBuilders.get(“XXX”)构造一个请求。
(3) ResultActions.param添加请求传值
(4) ResultActions.accept()设置返回类型
(5) ResultActions.andExpect添加执行完成后的断言。
(6) ResultActions.andDo添加一个结果处理器,表示要对结果做点什么事情,比如处使用print()输出整个响应结果信息。
(7) ResultActions.andReturn表示执行完成后返回相应的结果。
实例化
方法一:通过参数指定一组控制器,这样就不需要从上下文获取
了
mockMvC = MockMvcBuilders.standaloneSetup(new HelloWorldController()).build();
方法二:指定WebApplicationContext 将会从该上下文获得相应的控制器并得到相应的MockMvc
mockMVC=MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
步骤
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
1、双击选中你要测试的类,点击 Navigate
2、注意点击 Test 的时候鼠标要放在对应的类上
3、勾选你想要的
这时候生成的代码是这样的
package com.zhangyu.controller;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class HelloWorldControllerTest {
@BeforeEach
void setUp() {
}
@AfterEach
void tearDown() {
}
@Test
void index() {
}
}
根据上面的 MockMVC的基本步骤 我们编写代码
package com.zhangyu.controller;
import org.junit.jupiter.api.Test;
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.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
@AutoConfigureMockMvc
class HelloWorldControllerTest {
@Autowired
private MockMvc mvc;
@Test
void index() throws Exception {
mvc.perform(MockMvcRequestBuilders.get("/hello").accept(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(MockMvcResultHandlers.print())
.andReturn();
}
}
稍微解释一下这个几个新的注解:
- @SpringBootTest:获取启动类,加载配置,寻找主配置启动类(被 @SpringBootApplication 注解的)
- @RunWith(SpringRunner.class):让JUnit运行Spring的测试环境,获得Spring环境的上下文的支持。
- @AutoConfigureMockMvc:是Spring Boot提供的注解,它允许在不启动整个Web服务器的情况下测试控制器。确保Spring的上下文被正确加载
JUnit5常用注解及测试
@Test :表示方法是测试方法。但是与JUnit4的@Test不同,他的职责非常单一不能声明任何属性,拓展的测试将会由Jupiter提供额外测试
@ParameterizedTest :表示方法是参数化测试,下方会有详细介绍
@RepeatedTest :表示方法可重复执行,下方会有详细介绍
@DisplayName :为测试类或者测试方法设置展示名称
@BeforeEach :表示在每个单元测试之前执行
@AfterEach :表示在每个单元测试之后执行
@BeforeAll :表示在所有单元测试之前执行
@AfterAll :表示在所有单元测试之后执行
@Tag :表示单元测试类别,类似于JUnit4中的@Categories
@Disabled :表示测试类或测试方法不执行,类似于JUnit4中的@Ignore
@Timeout :表示测试方法运行如果超过了指定时间将会返回错误
@ExtendWith :为测试类或测试方法提供扩展类引用
MockMvc
public void getAllCategoryTest() throws Exception
{
String responseString = mockMvc.perform
(
MockMvcRequestBuilders.post("http://127.0.0.1:8888/login") //请求的url,请求的方法是post
//get("/user/showUser2") //请求的url,请求的方法是get
.contentType(MediaType.APPLICATION_FORM_URLENCODED)//发送数据的格式
.param("username","hyh") //添加参数(可以添加多个)
.param("password","123") //添加参数(可以添加多个)
)
//.andExpect(status().isOk()) //返回的状态是200
.andDo(print()) //打印出请求和相应的内容
.andReturn().getResponse().getContentAsString(); //将相应的数据转换为字符串
System.out.println("-----返回的json = " + responseString);
}
- mockMvc.perform执行一个RequestBuilder请求,会自动执行SpringMVC的流程并映射到相应的控制器执行处理;
- MockMvcRequestBuilders.post(“http://127.0.0.1:8888/login“)构造一个请求
- ResultActions.andExpect添加执行完成后的断言
- ResultActions.andDo添加一个结果处理器,表示要对结果做点什么事情,比如此处使用MockMvcResultHandlers.print()输出整个响应结果信息。
- ResultActions.andReturn表示执行完成后返回相应的结果。
mockMvc.perform(post("/user").param("name", "admin")) //执行请求
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserControllerTest {
@Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
// 这个方法在每个方法执行之前都会执行一遍
@Before
public void setup() {
mockMvc = MockMvcBuilders.webAppContextSetup(wac).build(); //指定WebApplicationContext,将会从该上下文获取相应的控制器并得到相应的MockMvc
}
@Test
public void whenUploadSuccess() throws Exception {
String result = mockMvc
.perform(fileUpload("/file").file(new MockMultipartFile("file", "test.txt",
"multipart/form-data", "hello upload".getBytes("UTF-8"))))
.andExpect(status().isOk()).andReturn().getResponse().getContentAsString();
System.out.println(result);
}
@Test
public void whenQuerySuccess() throws Exception {
String result = mockMvc
.perform(get("/user").param("username", "jojo").param("age", "18")
.param("ageTo", "60").param("xxx", "yyy")
// .param("size", "15")
// .param("page", "3")
// .param("sort", "age,desc")
.contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(status().isOk()).andExpect(jsonPath("$.length()").value(3)).andReturn()
.getResponse().getContentAsString();
System.out.println(result);
}
@Test
public void whenGetInfoSuccess() throws Exception {
String result = mockMvc.perform(get("/user/1").contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(status().isOk()).andExpect(jsonPath("$.username").value("tom"))
.andReturn().getResponse().getContentAsString();
System.out.println(result);
}
@Test
public void whenGetInfoFail() throws Exception {
mockMvc.perform(get("/user/a").contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(status().is4xxClientError());
}
@Test
public void whenCreateSuccess() throws Exception {
Date date = new Date();
System.out.println(date.getTime());
String content = "{\"username\":\"tom\",\"password\":\"root\",\"birthday\":" + date.getTime()
+ "}";
String reuslt = mockMvc
.perform(
post("/user").contentType(MediaType.APPLICATION_JSON_UTF8).content(content))
.andExpect(status().isOk()).andExpect(jsonPath("$.id").value("1")).andReturn()
.getResponse().getContentAsString();
System.out.println(reuslt);
}
@Test
public void whenCreateFail() throws Exception {
Date date = new Date();
System.out.println(date.getTime());
String content = "{\"username\":\"tom\",\"password\":null,\"birthday\":" + date.getTime()
+ "}";
String reuslt = mockMvc
.perform(
post("/user").contentType(MediaType.APPLICATION_JSON_UTF8).content(content))
// .andExpect(status().isOk())
// .andExpect(jsonPath("$.id").value("1"))
.andReturn().getResponse().getContentAsString();
System.out.println(reuslt);
}
@Test
public void whenUpdateSuccess() throws Exception {
Date date = new Date(LocalDateTime.now().plusYears(1).atZone(ZoneId.systemDefault())
.toInstant().toEpochMilli());
System.out.println(date.getTime());
String content = "{\"id\":\"1\", \"username\":\"tom\",\"password\":null,\"birthday\":"
+ date.getTime() + "}";
String reuslt = mockMvc
.perform(put("/user/1").contentType(MediaType.APPLICATION_JSON_UTF8)
.content(content))
.andExpect(status().isOk()).andExpect(jsonPath("$.id").value("1")).andReturn()
.getResponse().getContentAsString();
System.out.println(reuslt);
}
@Test
public void whenDeleteSuccess() throws Exception {
mockMvc.perform(delete("/user/1").contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(status().isOk());
}
}
断言
这些断言方法都是 org.junit.jupiter.api.Assertions 的静态方法。
JUnit4 升级 Junit5
在进行迁移的时候需要注意如下的变化:
- 注解在 org.junit.jupiter.api 包中,断言在 org.junit.jupiter.api.Assertions 类中,前置条件在 org.junit.jupiter.api.Assumptions 类中。
- 把@Before 和@After 替换成@BeforeEach 和@AfterEach。
- 把@BeforeClass 和@AfterClass 替换成@BeforeAll 和@AfterAll。
- 把@Ignore 替换成@Disabled。
- 把@Category 替换成@Tag。
- 把@RunWith、@Rule 和@ClassRule 替换成@ExtendWith。