JUnit 5
(JUnit Jupiter)已经存在了相当长的一段时间,并且配备了许多功能。 但令人意外JUnit 5
它不是一个默认的测试库相关,当涉及到春节开机测试入门:它仍然是JUnit 4.12
,在2014年发布了回来,如果你考虑使用JUnit 5
对你未来基于Spring启动项目,然后这篇博客文章是给你的。 您将通过针对不同用例的Spring Boot测试示例,了解基于Gradle
和Maven
的项目的基本设置。
源代码
可以在Github上找到本文的源代码: https : //github.com/kolorobot/spring-boot-junit5 。
从头开始设置项目
对于项目设置,您将需要JDK 11或更高版本以及Gradle或Maven(取决于您的偏好)。 开始使用Spring Boot的最简单方法是使用https://start.spring.io上的Initializr。 选择的唯一依赖项是Spring Web
。 无论您在生成的项目中使用什么依赖项,始终都包含测试依赖项( Spring Boot Starter Test
)。
用Gradle构建
使用Initializr
生成的Gradle构建的默认项目文件( gradle.build
):
plugins { id 'org.springframework.boot' version '2.1.8.RELEASE' id 'io.spring.dependency-management' version '1.0.8.RELEASE' id 'java' } group = 'pl.codeleak.samples' version = '0.0.1-SNAPSHOT' sourceCompatibility = '11' repositories { mavenCentral() } dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' testImplementation 'org.springframework.boot:spring-boot-starter-test' }
为了增加对JUnit 5
支持,我们需要排除旧的JUnit 4
依赖性,并包括JUnit 5
(JUnit Jupiter)依赖性:
dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' testImplementation( 'org.springframework.boot:spring-boot-starter-test' ) { exclude group: 'junit' , module: 'junit' } testCompile 'org.junit.jupiter:junit-jupiter:5.5.2' } test { useJUnitPlatform() testLogging { events "passed" , "skipped" , "failed" } }
用Maven构建
使用Initializr
生成的Maven构建的默认项目文件( pom.xml
):
<? xml version = "1.0" encoding = "UTF-8" ?> < project > < modelVersion >4.0.0</ modelVersion > < parent > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-starter-parent</ artifactId > < version >2.1.8.RELEASE</ version > < relativePath /> <!-- lookup parent from repository --> </ parent > < groupId >pl.codeleak.samples</ groupId > < artifactId >spring-boot-junit5</ artifactId > < version >0.0.1-SNAPSHOT</ version > < name >spring-boot-junit5</ name > < description >Demo project for Spring Boot and JUnit 5</ description > < properties > < project.build.sourceEncoding >UTF-8</ project.build.sourceEncoding > < java.version >11</ java.version > </ properties > < dependencies > < dependency > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-starter-web</ artifactId > </ dependency > < dependency > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-starter-test</ artifactId > < scope >test</ scope > </ dependency > </ dependencies > < build > < plugins > < plugin > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-maven-plugin</ artifactId > </ plugin > </ plugins > </ build > </ project >
为了增加对JUnit 5
支持,我们需要排除旧的JUnit 4
依赖性,并包括JUnit 5
(JUnit Jupiter)依赖性:
< properties > < junit.jupiter.version >5.5.2</ junit.jupiter.version > </ properties > < dependencies > < dependency > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-starter-web</ artifactId > </ dependency > < dependency > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-starter-test</ artifactId > < scope >test</ scope > < exclusions > < exclusion > < groupId >junit</ groupId > < artifactId >junit</ artifactId > </ exclusion > </ exclusions > </ dependency > < dependency > < groupId >org.junit.jupiter</ groupId > < artifactId >junit-jupiter</ artifactId > < version >${junit.jupiter.version}</ version > < scope >test</ scope > </ dependency > </ dependencies >
在测试类中使用JUnit 5
Initializr
生成的测试包含自动生成的JUnit 4
测试。 要应用JUnit 5
我们需要更改导入并将JUnit 4
替换为JUnit 5
扩展。 我们还可以使类和测试方法包受到保护:
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit.jupiter.SpringExtension; @ExtendWith (SpringExtension. class ) @SpringBootTest class SpringBootJunit5ApplicationTests { @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
实现,该TaskRepository
在内部使用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
配置应用程序,该配置JsonPlaceholderApiConfig
使用JsonPlaceholderApiConfigProperties
绑定来自application.properties
一些明智的属性:
@Configuration @EnableConfigurationProperties (JsonPlaceholderApiConfigProperties. class ) 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); } }
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测试
@ExtendWith (SpringExtension. class ) @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 ); } }
在具有模拟依赖项的随机端口上运行Web服务器的Spring Boot测试
@ExtendWith (SpringExtension. class ) @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测试
@ExtendWith (SpringExtension. class ) @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测试
@ExtendWith (SpringExtension. class ) @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测试
@ExtendWith (SpringExtension. class ) @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测试
@ExtendWith (SpringExtension. class ) @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
运行所有./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
参考文献
- https://docs.spring.io/spring-boot/docs/2.1.8.RELEASE/reference/html/boot-features-testing.html
- https://spring.io/guides/gs/testing-web/
- https://github.com/spring-projects/spring-boot/issues/14736
翻译自: https://www.javacodegeeks.com/2019/09/spring-boot-testing-junit-5.html