java单元测试写法
主要借助 Mockito + junit 框架
首先给一段代码样例,后面会逐步解释
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;// 使用Mockito扩展(替代手动调用MockitoAnnotations.openMocks)
@ExtendWith(MockitoExtension.class)
class PaymentServiceTest {// 模拟对象@Mockprivate PaymentGateway paymentGateway;@Mockprivate UserRepository userRepository;// 注入模拟对象到被测试类@InjectMocksprivate PaymentService paymentService;private PaymentRequest testRequest;// 测试数据初始化(@BeforeEach替代setup命名)@BeforeEachvoid initTestData() {testRequest = new PaymentRequest("user123", 100.0, "USD");}// 正常流程测试@Testvoid shouldProcessPaymentWhenUserAndBalanceValid() {// 1. 模拟依赖行为when(userRepository.findUser("user123")).thenReturn(new User("user123", 200.0));when(paymentGateway.process(any(PaymentRequest.class))).thenReturn(new PaymentResult(true, "success"));// 2. 调用被测试方法PaymentResult result = paymentService.processPayment(testRequest);// 3. 验证结果assertEquals("success", result.getCode());assertTrue(result.isSuccess());// 4. 验证交互行为verify(paymentGateway).process(testRequest);}// 异常流程测试@Testvoid shouldThrowExceptionWhenUserNotFound() {// 模拟用户不存在when(userRepository.findUser("user123")).thenReturn(null);// 验证异常抛出UserNotFoundException ex = assertThrows(UserNotFoundException.class,() -> paymentService.processPayment(testRequest));assertEquals("User user123 not found", ex.getMessage());// 验证paymentGateway未被调用verify(paymentGateway, never()).process(any());}// 边界条件测试@Testvoid shouldRejectPaymentWhenInsufficientBalance() {// 模拟用户余额不足when(userRepository.findUser("user123")).thenReturn(new User("user123", 50.0));InsufficientBalanceException ex = assertThrows(InsufficientBalanceException.class,() -> paymentService.processPayment(testRequest));assertTrue(ex.getMessage().contains("Insufficient balance"));verify(paymentGateway, never()).process(any());}
}
首先,按规范,测试类的类名 为 被测试类的类名+Test,比如PaymentService的测试类叫PayementServiceTest
接着,为什么写单元测试?你可以简单理解为我们按预期构造一些模拟数据,或者模拟返回结果;然后做真实的调用,根据真实调用返回的数据和模拟结果做比较,从而判断代码逻辑是否正确
接下来介绍各个以前没见过的地方
(一)@Mock 和 @InjectMocks
@Mock 是创建一个模拟依赖对象,我们用模拟对象做出的行为不会有真实的作用
标记了@Mock的对象,一般用它定义模拟行为
@InjectMocks,创建一个真实对象,我们用真实对象做出的行为会有真实的反馈
标记了@InjectMocks的对象,一般调用它去获取真实的数据
同时,@Mock用于构建假依赖,创建的对象会注入到@InjectMocks创建的对象中,替代真实依赖
(二)@BeforeEach 和 setup()函数
@BeforeEach是初始化方法,这个测试类中的每个测试方法执行前都必须先运行加了该注解的方法,用来做预操作(相应的还有@AfterEach)
setup()没什么特别的只是一般命名规范是这样,常和@BeforeEach搭配
private AutoCloseable closeable;@BeforeEach
void setup() {closeable = MockitoAnnotations.openMocks(this); // 返回可关闭对象
}@AfterEach
void tearDown() throws Exception {closeable.close(); // 显式释放资源
}
又冒出新问题了,MockitoAnnotations.openMocks(this) 是什么?
其实就是初始化上面加了@Mock 和 @InjectMocks的对象,没啥特别的
跟MockitoAnnotations.openMocks(this)等价的有一种,是加在测试类(不是测试方法)上的
@ExtendWith(MockitoExtension.class)
class MyTest {@Mockprivate Dependency dependency;// 无需setup方法
}
(三)Mockito.when
首先,Mockito.when和when的效果是一样的,when只是java的一种语法糖
用来模拟对象的行为,当某个方法被调用时,指定它要返回什么
所以常常用来构造返回预期
(补充:when构建用的多的情景是这种:我们在测试方法里很少直接走完整个流程,为了解耦,我们会单独测controller的部分,service部分,dao部分。因此比如我们调用controller,我们就会给when配当service被调用时返回一个什么,然后测controller有没有问题;比如好奇service有没有问题,就该调用真实的service,然后when那里设置dao层被调用时返回什么,这样就可以知道sevice有没有问题,因为如果没有问题,就能走到dao部分,获得正确返回)
与其搭配的函数有几个
① thenReturn 模拟返回值(支持多次返回不一样结果)
// 单次返回值,调用mockList.get(0)时返回"first"
Mockito.when(mockList.get(0)).thenReturn("first");// 多次调用返回不同值(依次返回)
Mockito.when(mockList.size()).thenReturn(1).thenReturn(2); // 第一次调用返回1,第二次返回2
② thenThrow 模拟抛异常
Mockito.when(mockList.clear()).thenThrow(new RuntimeException("清理失败"));
③ thenAnswer 模拟返回 自定义逻辑
Mockito.when(mockList.get(anyInt())).thenAnswer(invocation -> {int index = invocation.getArgument(0);return "element-" + index;});
还有几个,貌似用的不多,查一下就行
(四)Assertions 断言(忘记版本了,如果不对那就Assert)
①assertEquals
比较上面mock构造的预期结果和真实调用的结果,验证测试结果是否符合预期
用法:Assertions.assertEquals(预期结果,真实返回结果);
②assertTrue 和 assertFalse
验证某个方法的调用是不是是否成功或失败
常用来验证删除等操作是否成功
用法:Assertions.assertTrue(boolean); 传一个方法返回值,assertFalse的同理
③assertNull 和 assertNotnull
验证某个方法的返回值是空或非空
用法:Assertions.assertNull(Object); notnull 的同理
④assertThrows
验证某个方法调用后是否报异常
@Test
public void testUserNotFound() {// 模拟依赖:查询不存在的用户时抛出异常Mockito.when(userRepository.findById(999L)).thenThrow(new UserNotFoundException("用户不存在"));// 验证调用时抛出预期异常assertThrows(UserNotFoundException.class, () -> userService.getUser(999L));
}