现在写单元测试的重要性不言而喻,下边说明一下Junit5测试的会用到的主要注解和方法。PS:常用开发工具都可以自动生成Junit测试类。
一. 单元测试命名
可参考【翻译】7种流行的单元测试命名约定
在项目种我选择的是should...when的写法,但需注意方法命名时不要过长,过长反而导致难于阅读和理解
二. 常用注解
-
类注解
- @SpringbootTest 加在类上,会启动整个环境;做集成测试;由于controller层逻辑少,一般用在controller层;比较慢
- @ExtendWith(SpringExtension.class) Junit5中用来替代Junit4的@RunWith(SpringJUnit4ClassRunner.class),会启动Spring的上下文
- @ContextConfiguration 指定加载ApplicationContext的配置文件或配置类,一般和@ExtendWith(SpringExtension.class)结合使用
- @ExtendWith(MockitoExtension.class) Junit5中用来替代JUnit4 @RunWith(MockitoJUnitRunner.class)。不需要Spring上下文时用该注解
-
模拟类或接口的注解
- @Mock、@InjectMocks、@Spy Mockito库提供的注解;@Mock创建一个Mock对象,@InjectMocks创建一个实例,尝试将其它有@Mock或@Spy的对象注入到该实例中;@Spy默认会调用真实的方法,@Mock默认不执行
- @MockBean、@SpyBean Spring Boot包装Mockito库提供的注解;@MockBean创建的对象默认会加入Spring的上下文中,如果Spring的上下文存在则替换;@MockBean和@SpyBean区别同@Mock和@Spy的区别
-
方法注解
- Test 同Junit4
-
常用测试方法
- Mockito.when().thenReturn()
- Mockito.doNothing().when(类).方法
- Assertions.assertTrue
- Assertions.assertNotNull
- Mockito.verify(bean, times(次数)).方法
- Assertions.assertThrows(Class expectedType, () -> 类.方法)
三. 测试方法结构
单元测试采用Given...When...Then的结构,即
- 准备数据,用mock模拟方法返回值
- 执行,调用测试方法
- 验证,用assert等验证方法返回结果
四. 数据库的单元测试
@MybatisPlusTest
只注入MybatisPlus相关Bean做单元测试,比较像@Mybatis注解,由mybatis-plus的3.4.0版本后的mybatis-plus-boot-starter-test模块提供。
可以在做数据库的单元测试时不使用@SpringBootTest注解启动整个工程
五. 接入层的单元测试
@WebMvcTest
同样用来做MVC层的单元测试,只注入MVC层相关的Bean
六. demo
- pom依赖
<dependency>
<groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter-test</artifactId>
<version>3.4.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
- dao层单元测试
@MybatisPlusTest
@ContextConfiguration(classes = { JunitDemo.class })
class UserMapperTest {
@Autowired
private UserMapper userMapper;
@Test
void should_select5Items_when_afterInit() {
List<User> users = userMapper.selectList(null);
Assertions.assertEquals(5, users.size());
}
}
- service层单元测试
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserMapper userMapper;
@InjectMocks
private UserService userService;
@Test
void should_returnResult_when_queryById() {
// Given 准备数据:初始状态或前置条件
long userId = 1;
User mockUser = new User();
mockUser.setId(userId);
mockUser.setName("li");
Mockito.doReturn(mockUser).when(userMapper).selectById(userId);
// When 执行:行为发生
User user = userService.getOnlyUser(0);
// Then 验证:断言结果
Mockito.verify(userMapper, Mockito.times(1)).selectById(userId);
Assertions.assertEquals("li", user.getName());
}
/**
* 小于18岁入库抛出异常
*/
@Test
void should_businessException_when_ageLessThan18() {
Mockito.doNothing().when(userMapper).insert(Mockito.isA(User.class));
Assertions.assertThrows(BusinessException.class, () -> userService.insertUser(buildUserAgeLessThan18()));
}
}
- RestController的集成测试
@SpringBootTest(classes = JunitDemo.class)
@AutoConfigureMockMvc
class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
void should_returnUser_when_query() throws Exception {
mockMvc.perform(
MockMvcRequestBuilders.get("/user/getOnlyUser?userId=1").contentType(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("name").value("xiaoming"))
.andExpect(MockMvcResultMatchers.jsonPath("age").value("18")).andReturn();
}
}