Test in SpringBoot
maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
从SpringBoot2.4开始,Junit5老式驱动已经被SpringBootTest启动依赖移除,如果我们想使用Junit4,我们需要添加以下依赖
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
</exclusion>
</exclusions>
</dependency>
使用@SpringBootTest进行进行集成测试
见名知义,集成测试就是侧重于集成应用程序的不同层,这也就意味着不涉及到Mock。
因为集成测试很耗时以及需要一个实际的数据库来执行,所以我们应该尽量将集成测试与单元测试分开,而不是一起运行。我们可以通过使用不同的配置文件来只运行集成测试。
集成测试需要启动一个容器来执行测试用例。
@RunWith(SpringRunner.class)
@SpringBootTest(
SpringBootTest.WebEnvironment.MOCK,
classes = Application.class)
@AutoConfigureMockMvc
@TestPropertySource(
locations = "classpath:application-integrationtest.properties")
public class EmployeeRestControllerIntegrationTest {
@Autowired
private MockMvc mvc;
@Autowired
private EmployeeRepository repository;
// write test cases here
}
@SpringBootTest 注解可以通过创建将在测试中使用的ApplicationContext来引导容器。
@SpringBootTest 注解可以配置webEnvironment属性,WebEnvironment.MOCK表示容器可以模拟servlet环境。
TestPropertySource 注解可以设置该测试的配置文件。加载了TestPropertySource的属性文件将覆盖现有的application.properties 文件。配置文件可以写入如数据库等的属性配置。
集成测试的测试用例可能类似于控制层单元测试:
@Test
public void givenEmployees_whenGetEmployees_thenStatus200()
throws Exception {
createTestEmployee("bob");
mvc.perform(get("/api/employees")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content()
.contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$[0].name", is("bob")));
}
与控制器层单元测试的不同之处在于,此处不会Mock任何内容,并且将执行端到端方案。
用@SpringBootTest注释的测试将引导完整的应用程序上下文,这意味着我们可以将组件扫描获取的任何bean@Autowire到我们的测试中
@RunWith(SpringRunner.class)
@SpringBootTest
public class EmployeeServiceImplIntegrationTest {
@Autowired
private EmployeeService employeeService;
// class code ...
}
我们可以利用@TestConfiguration来创建一个单独的测试配置类,这个类不会被正常的组件扫描添加成为bean。因此我们需要利用@Import注解来对每个用到它的测试中进行引入,以及使用@Autowired使用。
@TestConfiguration
public class EmployeeServiceImplTestContextConfiguration {
@Bean
public EmployeeService employeeService() {
return new EmployeeService() {
// implement methods
};
}
}
---------------------------------------------------------------------
@RunWith(SpringRunner.class)
@Import(EmployeeServiceImplTestContextConfiguration.class)
public class EmployeeServiceImplIntegrationTest {
@Autowired
private EmployeeService employeeService;
// remaining class code
}
当我们需要测试Service层时,我们需要用到持久层的repository,但是测试持久层时,我们不应该关心持久层是否被实现了。SpringBootTest为我们提供了Mocking来解决这个问题。
@RunWith(SpringRunner.class)
public class EmployeeServiceImplIntegrationTest {
@TestConfiguration
static class EmployeeServiceImplTestContextConfiguration {
@Bean
public EmployeeService employeeService() {
return new EmployeeServiceImpl();
}
}
@Autowired
private EmployeeService employeeService;
@MockBean
private EmployeeRepository employeeRepository;
// write test cases here
}
@MockBean为 EmployeeRepository 创建一个 Mock,可以绕过实际对EmployeeRepository的调用。
@Before
public void setUp() {
Employee alex = new Employee("alex");
Mockito.when(employeeRepository.findByName(alex.getName()))
.thenReturn(alex);
}
@Test
public void whenValidName_thenEmployeeShouldBeFound() {
String name = "alex";
Employee found = employeeService.getEmployeeByName(name);
assertThat(found.getName())
.isEqualTo(name);
}
当我们为持久层做集成测试的时候。我们需要用到@DataJpaTest
如下:
@Entity
@Table(name = "person")
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Size(min = 3, max = 20)
private String name;
// standard getters and setters, constructors
}
@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
public Employee findByName(String name);
}
@RunWith(SpringRunner.class)
@DataJpaTest
public class EmployeeRepositoryIntegrationTest {
@Autowired
private TestEntityManager entityManager;
@Autowired
private EmployeeRepository employeeRepository;
// write test cases here
}
@RunWith(SpringRunner.class)提供了Spring Boot Test与Junit之间的桥梁,每当我们在Junit测试中使用任何Spring Boot测试功能时,都需要该注解。
@DataJpaTest提供了测试持久层的一些标准设置
- 配置H2内存储存数据库
- 设置Hibernate, Spring Data,和数据源
- 执行@EntityScan
- 打开SQL日志记录
Spring Boot TestEntityManager 是JPA EntityManager 的标准替代方法,提供了编写测试时常用的方法。可以为我们在数据库中设置一些数据。
@Test
public void whenFindByName_thenReturnEmployee() {
// given
Employee alex = new Employee("alex");
entityManager.persist(alex);
entityManager.flush();
// when
Employee found = employeeRepository.findByName(alex.getName());
// then
assertThat(found.getName())
.isEqualTo(alex.getName());
}
上面的测试中,我们使用测试实体管理器在数据库中插入一个 Employee,并通过按名称查找 API 读取它。
使用@WebMvcTest进行单元测试
@RestController
@RequestMapping("/api")
public class EmployeeRestController {
@Autowired
private EmployeeService employeeService;
@GetMapping("/employees")
public List<Employee> getAllEmployees() {
return employeeService.getAllEmployees();
}
}
@RunWith(SpringRunner.class)
@WebMvcTest(EmployeeRestController.class)
public class EmployeeRestControllerIntegrationTest {
@Autowired
private MockMvc mvc;
@MockBean
private EmployeeService service;
// write test cases here
}
为了测试控制器,我们使用@WebMvcTest。它将为我们的单元测试自动配置Spring MVC基础架构
大多数情况下,@WebMvcTest 将被限制为引导单个控制器。我们还可以将其与@MockBean一起使用,为任何所需的依赖项提供模拟实现。
@WebMvcTest还自动配置了MockMvc,它提供了一种强大的方法来轻松测试MVC控制器,而无需启动完整的HTTP服务器。
@Test
public void givenEmployees_whenGetEmployees_thenReturnJsonArray()
throws Exception {
Employee alex = new Employee("alex");
List<Employee> allEmployees = Arrays.asList(alex);
given(service.getAllEmployees()).willReturn(allEmployees);
mvc.perform(get("/api/employees")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$", hasSize(1)))
.andExpect(jsonPath("$[0].name", is(alex.getName())));
}