JUnit 5
(JUnit Jupiter)已经存在了相当长的一段时间,它配备了大量的功能,从Spring Boot 2.2开始, ,它是默认的测试库依赖。在这篇博文中,你会发现Spring Boot中的一些基本测试实例,以及针对基本Web应用的 。JUnit 5
JUnit 5
内容表
源代码
本文的源代码可以在Github上找到:https://github.com/kolorobot/spring-boot-junit5。
设置项目
Spring Boot 2.2增加了对JUnit Jupiter的默认支持。用Initializr
(https://start.spring.io)生成的每个项目都有所有需要的依赖,生成的测试类使用@SpringBootTest
注释,该注解将测试配置为JUnit 5。
package com.example.demo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class DemoApplicationTests {
@Test
void contextLoads() {
}
}
提示:如果你是JUnit 5的新手,请看我关于JUnit 5的其他帖子:https://blog.codeleak.pl/search/label/junit 5
运行测试
我们可以用Maven Wrapper
:./mvnw clean test
或Gradle Wrapper
:./gradlew clean test
来运行测试。
带有单个REST控制器的示例应用程序
该示例应用程序包含一个具有三个端点的单一REST控制器:
/tasks/{id}
/tasks
/tasks?title={title}
每个控制器的方法都在内部调用JSONPlaceholder- 用于测试和原型设计的假的在线REST API。
项目文件的结构如下:
$ tree src/main/java
src/main/java
└── pl
└── codeleak
└── samples
└── springbootjunit5
├── SpringBootJunit5Application.java
├── config
│ ├── JsonPlaceholderApiConfig.java
│ └── JsonPlaceholderApiConfigProperties.java
└── todo
├── JsonPlaceholderTaskRepository.java
├── Task.java
├── TaskController.java
└── TaskRepository.java
它也有以下静态资源:
$ tree src/main/resources/
src/main/resources/
├── application.properties
├── static
│ ├── error
│ │ └── 404.html
│ └── index.html
└── templates
TaskController
将其工作委托给TaskRepository
:
@RestController
class TaskController {
private final TaskRepository taskRepository;
TaskController(TaskRepository taskRepository) {
this.taskRepository = taskRepository;
}
@GetMapping("/tasks/{id}")
Task findOne(@PathVariable Integer id) {
return taskRepository.findOne(id);
}
@GetMapping("/tasks")
List<Task> findAll() {
return taskRepository.findAll();
}
@GetMapping(value = "/tasks", params = "title")
List<Task> findByTitle(String title) {
return taskRepository.findByTitle(title);
}
}
TaskRepository
是由JsonPlaceholderTaskRepository
实现的,它在内部使用RestTemplate
来调用JSONPlaceholder(https://jsonplaceholder.typicode.com) 端点:
public class JsonPlaceholderTaskRepository implements TaskRepository {
private final RestTemplate restTemplate;
private final JsonPlaceholderApiConfigProperties properties;
public JsonPlaceholderTaskRepository(RestTemplate restTemplate, JsonPlaceholderApiConfigProperties properties) {
this.restTemplate = restTemplate;
this.properties = properties;
}
@Override
public Task findOne(Integer id) {
return restTemplate
.getForObject("/todos/{id}", Task.class, id);
}
// other methods skipped for readability
}
应用程序是通过JsonPlaceholderApiConfig
配置的,它使用JsonPlaceholderApiConfigProperties
,从application.properties
绑定一些合理的属性:
@Configuration
public class JsonPlaceholderApiConfig {
private final JsonPlaceholderApiConfigProperties properties;
public JsonPlaceholderApiConfig(JsonPlaceholderApiConfigProperties properties) {
this.properties = properties;
}
@Bean
RestTemplate restTemplate() {
return new RestTemplateBuilder()
.rootUri(properties.getRootUri())
.build();
}
@Bean
TaskRepository taskRepository(RestTemplate restTemplate, JsonPlaceholderApiConfigProperties properties) {
return new JsonPlaceholderTaskRepository(restTemplate, properties);
}
}
注意:从Spring Boot 2.2开始,你不需要使用配置属性。
@EnableConfigurationProperties
application.properties
包含与JSONPlaceholder端点配置有关的几个属性:
json-placeholder.root-uri=https://jsonplaceholder.typicode.com
json-placeholder.todo-find-all.sort=id
json-placeholder.todo-find-all.order=desc
json-placeholder.todo-find-all.limit=20
在这篇博文中阅读更多关于
@ConfigurationProperties
: https://blog.codeleak.pl/2014/09/using-configurationproperties-in-spring.html
创建Spring Boot测试
Spring Boot提供了许多支持测试应用程序的实用程序和注解。
在创建测试时可以使用不同的方法。下面你会发现创建Spring Boot测试的最常见情况。
在随机端口上运行Web服务器的Spring Boot测试
在下面的测试中,将使用一个随机端口创建Web环境。然后,这个端口被注入到用@LocalServerPort
注释的字段中。在这种模式下,应用程序使用嵌入式服务器执行:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class TaskControllerIntegrationTest {
@LocalServerPort
private int port;
@Autowired
private TestRestTemplate restTemplate;
@Test
void findsTaskById() {
// act
var task = restTemplate.getForObject("http://localhost:" + port + "/tasks/1", Task.class);
// assert
assertThat(task)
.extracting(Task::getId, Task::getTitle, Task::isCompleted, Task::getUserId)
.containsExactly(1, "delectus aut autem", false, 1);
}
}
在Spring Boot测试中,Web服务器在随机端口上运行,并带有模拟的依赖性
如果你需要模拟任何Bean,你可以使用@MockBean
注解来标记任何依赖关系为模拟对象。Spring Boot使用Mockito创建模拟对象。在下面的例子中,应用程序将使用运行在默认端口的嵌入式服务器启动:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class TaskControllerIntegrationTestWithMockBeanTest {
@LocalServerPort
private int port;
@MockBean
private TaskRepository taskRepository;
@Autowired
private TestRestTemplate restTemplate;
@Test
void findsTaskById() {
// arrange
var taskToReturn = new Task();
taskToReturn.setId(1);
taskToReturn.setTitle("delectus aut autem");
taskToReturn.setCompleted(true);
taskToReturn.setUserId(1);
when(taskRepository.findOne(1)).thenReturn(taskToReturn);
// act
var task = restTemplate.getForObject("http://localhost:" + port + "/tasks/1", Task.class);
// assert
assertThat(task)
.extracting(Task::getId, Task::getTitle, Task::isCompleted, Task::getUserId)
.containsExactly(1, "delectus aut autem", true, 1);
}
}
带有模拟MVC层的Spring Boot测试
使用完全配置的嵌入式服务器来启动Spring Boot应用程序可能会很耗时,而且对于集成测试来说,这并不总是最好的选择。如果你在测试中不需要完整的服务器功能,你可以利用模拟的MVC层(MockMvc
)。这可以通过添加@AutoConfigureMockMvc
到@SpringBootTest
来实现:
@SpringBootTest
@AutoConfigureMockMvc
class TaskControllerMockMvcTest {
@Autowired
private MockMvc mockMvc;
@Test
void findsTaskById() throws Exception {
mockMvc.perform(get("/tasks/1"))
.andDo(print())
.andExpect(status().isOk())
.andExpect(content().json("{\"id\":1,\"title\":\"delectus aut autem\",\"userId\":1,\"completed\":false}"));
}
}
带有模拟MVC层和模拟依赖的Spring Boot测试
@MockedBean
可以与自动配置的 MockMvc
@SpringBootTest
@AutoConfigureMockMvc
class TaskControllerMockMvcWithMockBeanTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private TaskRepository taskRepository;
@Test
void findsTaskById() throws Exception {
// arrange
var taskToReturn = new Task();
taskToReturn.setId(1);
taskToReturn.setTitle("delectus aut autem");
taskToReturn.setCompleted(true);
taskToReturn.setUserId(1);
when(taskRepository.findOne(1)).thenReturn(taskToReturn);
// act and assert
mockMvc.perform(get("/tasks/1"))
.andDo(print())
.andExpect(status().isOk())
.andExpect(content().json("{\"id\":1,\"title\":\"delectus aut autem\",\"userId\":1,\"completed\":true}"));
}
}
带有模拟Web层的Spring Boot测试
如果只需要Web层(而不是上下文配置),你可以使用@WebMvcTest
:
@WebMvcTest
@Import(JsonPlaceholderApiConfig.class)
class TaskControllerWebMvcTest {
@Autowired
private MockMvc mockMvc;
@Test
void findsTaskById() throws Exception {
mockMvc.perform(get("/tasks/1"))
.andDo(print())
.andExpect(status().isOk())
.andExpect(content().json("{\"id\":1,\"title\":\"delectus aut autem\",\"userId\":1,\"completed\":false}"));
}
}
带有模拟Web层和模拟依赖的Spring Boot测试
@MockedBean
可以与 一起使用@WebMvcTest
@WebMvcTest
class TaskControllerWebMvcWithMockBeanTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private TaskRepository taskRepository;
@Test
void findsTaskById() throws Exception {
// arrange
var taskToReturn = new Task();
taskToReturn.setId(1);
taskToReturn.setTitle("delectus aut autem");
taskToReturn.setCompleted(true);
taskToReturn.setUserId(1);
when(taskRepository.findOne(1)).thenReturn(taskToReturn);
// act and assert
mockMvc.perform(get("/tasks/1"))
.andDo(print())
.andExpect(status().isOk())
.andExpect(content().json("{\"id\":1,\"title\":\"delectus aut autem\",\"userId\":1,\"completed\":true}"));
}
}
运行所有测试
我们可以使用Maven Wrapper
:./mvnw clean test
或Gradle Wrapper
:./gradlew clean test
来运行所有测试。
使用Gradle
运行测试的结果:
$ ./gradlew clean test
> Task :test
pl.codeleak.samples.springbootjunit5.SpringBootJunit5ApplicationTests > contextLoads() PASSED
pl.codeleak.samples.springbootjunit5.todo.TaskControllerWebMvcTest > findsTaskById() PASSED
pl.codeleak.samples.springbootjunit5.todo.TaskControllerIntegrationTestWithMockBeanTest > findsTaskById() PASSED
pl.codeleak.samples.springbootjunit5.todo.TaskControllerWebMvcWithMockBeanTest > findsTaskById() PASSED
pl.codeleak.samples.springbootjunit5.todo.TaskControllerIntegrationTest > findsTaskById() PASSED
pl.codeleak.samples.springbootjunit5.todo.TaskControllerMockMvcTest > findsTaskById() PASSED
pl.codeleak.samples.springbootjunit5.todo.TaskControllerMockMvcWithMockBeanTest > findsTaskById() PASSED
BUILD SUCCESSFUL in 7s
5 actionable tasks: 5 executed
参考文献
也请参见
- blog.codeleak.pl/search/labe…
- blog.codeleak.pl/2014/09/usi…
- blog.codeleak.pl/2015/03/spr…
- blog.codeleak.pl/2014/09/tes…
源代码
本文的源代码可以在Github上找到:https://github.com/kolorobot/spring-boot-junit5。