最近在看《代码整洁之道》《重构》这两本书
里面都着重的提到过,单元测试
说来惭愧以前的测试也是应付上级检查,所以对这块的认知并不足够
但要成为一名合格的开发者,还是应该把这一块捡起来
@Test
是最简单的了,没啥好说的
快速生成测试类 https://blog.youkuaiyun.com/jj_nan/article/details/64134781
基于RestTemplate测试http接口
SpringBoot Test集成测试_springboot 集成test-优快云博客
类似的HTTP客户端有Unirest、feign等
最简单的是加俩注解
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = 启动类Application.class)
但确定很明显,需要启动整个项目,限制太多了
这个时候就需要使用mock的形式
JMockit中文网 http://jmockit.cn/index.htm(教程简单易懂,非常推荐)
==========================================================
Mockit Baeldung中文网: Java教程, Spring Boot教程, Spring Cloud教程
由于网站有时候打不开, 所以记录一下使用过程
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
springBoot-test的依赖中, 包含了mockit
开始使用mockito的3种方式
1.类上加@ExtendWith(MockitoExtension.class)
2.手动初始化 MockitoAnnotations.openMocks(this);
3.使用注解@Rule 修饰成员变量 public MockitoRule initRule = MockitoJUnit.rule();
手动mock 或者 使用@Mock注解
// 手动
@Test
public void whenNotUseMockAnnotation_thenCorrect() {
List mockList = Mockito.mock(ArrayList.class);
mockList.add("one");
Mockito.verify(mockList).add("one");
assertEquals(0, mockList.size()); // size方法没有设置mock值, 前面add无效, 结果任然为0
Mockito.when(mockList.size()).thenReturn(100);
assertEquals(100, mockList.size());// mock了之后 就能返回指定的值
}
// 注解
@Mock
List<String> mockedList;
@Test
public void whenUseMockAnnotation_thenMockIsInjected() {
mockedList.add("one");
Mockito.verify(mockedList).add("one");
assertEquals(0, mockedList.size());
Mockito.when(mockedList.size()).thenReturn(100);
assertEquals(100, mockedList.size());
}
@Spy注解
spy和mock的区别, spy只影响指定的方法, mock是影响全部的方法
// 手动
@Test
public void whenNotUseMockAnnotation_thenCorrect() {
List mockList = Mockito.spy(ArrayList.class);
mockList.add("one");
Mockito.verify(mockList).add("one");
assertEquals(1, mockList.size()); // size方法可以生效, 值为1
Mockito.when(mockList.size()).thenReturn(100); // 只影响了size的值
assertEquals(100, mockList.size());
}
// 注解 略
@Captor
用于捕获方法的参数, 避免了手动写各种when来传递参数
// 手动
@Test
public void whenUseCaptorAnnotation_thenTheSame() {
List mockList = Mockito.mock(List.class);
ArgumentCaptor<String> arg = ArgumentCaptor.forClass(String.class);
mockList.add("one");
Mockito.verify(mockList).add(arg.capture());
assertEquals("one", arg.getValue());
}
// 使用注解
@Mock
List mockedList;
@Captor
ArgumentCaptor argCaptor;
@Test
public void whenUseCaptorAnnotation_thenTheSam() {
mockedList.add("one");
Mockito.verify(mockedList).add(argCaptor.capture());
assertEquals("one", argCaptor.getValue());
}
@InjectMocks
当serviceA 的方法, 依赖 serviceB时,
@InjectMocks能够把 用@Mock修饰的 serviceB, 自动注入到ServiceA中
注意: 被@Spy 修饰的对象, 无法自动注入, 只能手动通过构造方法传递
=========================
手动抛出异常
class MyDictionary {
private Map<String, String> wordMap;
public void add(String word, String meaning) {
wordMap.put(word, meaning);
}
public String getMeaning(String word) {
return wordMap.get(word);
}
}
// 有返回值时
@Test
void givenNonVoidReturnType_whenUsingWhenThen_thenExceptionIsThrown() {
MyDictionary dictMock = mock(MyDictionary.class);
when(dictMock.getMeaning(anyString())).thenThrow(NullPointerException.class);
assertThrows(NullPointerException.class, () -> dictMock.getMeaning("word"));
}
// 无返回值时
@Test
void givenVoidReturnType_whenUsingDoThrow_thenExceptionIsThrown() {
MyDictionary dictMock = mock(MyDictionary.class);
doThrow(IllegalStateException.class).when(dictMock)
.add(anyString(), anyString());
assertThrows(IllegalStateException.class, () -> dictMock.add("word", "meaning"));
}
以及 手动创建异常对象
// 有返回值时
@Test
void givenNonVoidReturnType_whenUsingWhenThenAndExeceptionAsNewObject_thenExceptionIsThrown() {
MyDictionary dictMock = mock(MyDictionary.class);
when(dictMock.getMeaning(anyString())).thenThrow(new NullPointerException("Error occurred"));
assertThrows(NullPointerException.class, () -> dictMock.getMeaning("word"));
}
// 无返回值时
@Test
void givenNonVoidReturnType_whenUsingDoThrowAndExeceptionAsNewObject_thenExceptionIsThrown() {
MyDictionary dictMock = mock(MyDictionary.class);
doThrow(new IllegalStateException("Error occurred")).when(dictMock)
.add(anyString(), anyString());
assertThrows(IllegalStateException.class, () -> dictMock.add("word", "meaning"));
}
======================================================================
======================================================================
======================================================================
项目中的使用记录
由于是使用了junit5, 所以不用写@RunWith
/**
* 不启动项目, 验证接口逻辑
*/
@ExtendWith(MockitoExtension.class)
public class UserServiceImplTest {
@InjectMocks
private UserServiceImpl service;
@Mock
private userMapper userMapper;
@Test
public void testListByIds() throws Throwable {
// mock结果
UserEntity obj = new UserEntity();
obj.setUnitId("test-unit-id");
List<UserEntity> mockResultList = new ArrayList<>();
mockResultList.add(obj);
// 设定mock
when(userMapper.selectList(ArgumentMatchers.any())).thenReturn(mockResultList);
// 调用public方法
// List<UserEntity> result = service.listByIds(ids);
// 反射调用private方法
Method privateMethod = UserServiceImpl.class.getDeclaredMethod("listByIds", List.class);
privateMethod.setAccessible(true);
final List<Long> ids = Arrays.stream(new Long[]{1L, 2L}).collect(Collectors.toList());
final List<UserEntity> result = (List<UserEntity>) privateMethod.invoke(service, ids);
// 断言
assertEquals(mockResultList.get(0).getUid(), result.get(0).getUid(), "verify failure, id不匹配");
}
}
/**
* 启动项目, 调用真实数据库
*/
@ActiveProfiles("dev")
@ExtendWith(MockitoExtension.class)
@SpringBootTest
@Slf4j
public class UserServiceImplTest {
@Autowired
private UserServiceImpl service;
@Test
public void testMaxSeq() {
MaxSeqBO result = service.getMaxSeq("area-code", "group-code");
log.info("Database result: {}", JSONUtil.toJsonPrettyStr(result));
// 断言
assertEquals(30, result.getMaxSeq(), "verify failure");
}
}
/**
* 启动springBoot, 调用mock数据
*
* @Test注解是基于junit5(jupiter包路)
* 参考
* https://www.baeldung-cn.com/injecting-mocks-in-spring
*/
@ActiveProfiles("dev")
@ExtendWith(MockitoExtension.class)
@SpringBootTest
@Slf4j
public class UserServiceImplTest {
@Autowired
private UserServiceImpl service;
/**
* 此处不能用 @Autowired, 要用@MockBean
*/
@MockBean
private UserMapper userMapper;
/**
* 需要注册一个mock的bean
*/
@Bean
@Primary
public UserMapper nameService() {
return Mockito.mock(UserMapper.class);
}
@Test
public void testMaxSeq() {
// mock返回结果
MaxSeqBO mockBO = new MaxSeqBO();
mockBO.setMoveSeq(10);
// 设定mock
Mockito.when(UserMapper.getMaxSeq(ArgumentMatchers.any(), ArgumentMatchers.any())).thenReturn(mockBO);
// 调用
MaxSeqBO result = service.getMaxSeq("area-code", "group-code");
log.info("Database result: {}", JSONUtil.toJsonPrettyStr(result));
// 断言
assertEquals(10, result.getMoveSeq(), "verify failure");
}
}