使用JUnit 5进行Spring Boot测试

JUnit 5 (JUnit Jupiter)已经存在了相当长的一段时间,并且配备了许多功能。 但令人意外JUnit 5它不是一个默认的测试库相关,当涉及到春节开机测试入门:它仍然JUnit 4.12 ,在2014年发布了回来,如果你考虑使用JUnit 5对你未来基于Spring启动项目,然后这篇博客文章是给你的。 您将通过针对不同用例的Spring Boot测试示例,了解基于GradleMaven的项目的基本设置。

源代码

可以在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 ):

   < project > 
     < modelVersion >4.0.0 
     < parent > 
         < groupId >org.springframework.boot 
         < artifactId >spring-boot-starter-parent 
         < version >2.1.8.RELEASE 
         < relativePath />  
      
     < groupId >pl.codeleak.samples 
     < artifactId >spring-boot-junit5 
     < version >0.0.1-SNAPSHOT 
     < name >spring-boot-junit5 
     < description >Demo project for Spring Boot and JUnit 5 
     < properties > 
         < project.build.sourceEncoding >UTF-8 
         < java.version >11 
      
     < dependencies > 
         < dependency > 
             < groupId >org.springframework.boot 
             < artifactId >spring-boot-starter-web 
          
         < dependency > 
             < groupId >org.springframework.boot 
             < artifactId >spring-boot-starter-test 
             < scope >test 
          
      
     < build > 
         < plugins > 
             < plugin > 
                 < groupId >org.springframework.boot 
                 < artifactId >spring-boot-maven-plugin 
              
          
        

为了增加对JUnit 5支持,我们需要排除旧的JUnit 4依赖性,并包括JUnit 5 (JUnit Jupiter)依赖性:

 < properties > 
     < junit.jupiter.version >5.5.2    < dependencies > 
     < dependency > 
         < groupId >org.springframework.boot 
         < artifactId >spring-boot-starter-web 
      
     < dependency > 
         < groupId >org.springframework.boot 
         < artifactId >spring-boot-starter-test 
         < scope >test 
         < exclusions > 
             < exclusion > 
                 < groupId >junit 
                 < artifactId >junit 
              
          
      
     < dependency > 
         < groupId >org.junit.jupiter 
         < artifactId >junit-jupiter 
         < version >${junit.jupiter.version} 
         < scope >test 
        

在测试类中使用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 testGradle 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); 
     }  } 

TaskRepositoryJsonPlaceholderTaskRepository实现,该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 testGradle 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

你可能感兴趣的:(使用JUnit 5进行Spring Boot测试)