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 />
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 findAll() {
return taskRepository.findAll();
}
@GetMapping (value = "/tasks" , params = "title" )
List 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