当前位置: 首页 > backend >正文

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));
}

http://www.xdnf.cn/news/13629.html

相关文章:

  • Element:Table表头全部或单个表头颜色header-row-style
  • 鸿蒙开发:DevEcoTesting中的稳定性测试
  • 【Python 算法零基础 6.贪心算法】
  • 基于51单片机和8X8点阵屏、独立按键的跳跃躲闪类小游戏
  • 在WordPress中彻底关闭生成缩略图的方法
  • c++经典好题
  • 机器学习基本概念与建模流程
  • day031-Shell自动化编程-数组与案例
  • 【全开源】码小象租车系统源码+uniapp前端+开发文档接口
  • 【粤语ASRTTS】粤语语音识别与合成:重塑粤语智能化标杆
  • android studio开关控制
  • 小知识点二:无刷电机开环控制
  • 创建型设计模式
  • linux系统实时监控top命令
  • Spring Boot 完整教程 - 从入门到精通(全面版)
  • 鹰盾视频加密器播放器倍速播放中变速不变声的技术原理解析
  • 开源生态新势能: 驱动国产 DevSecOps 与 AI 工程新进展
  • DeepSeek-R1与Claude 4.0 Sonnet:开源与闭源大模型的商业生态博弈
  • 快速生成树协议(RSTP)深度解析
  • 基于 tinyfsm 的状态机
  • 未来智能设备的三大核心能力:自检测、自修复与自决策
  • 套接字类型与协议设置
  • 微信小程序之页面跳转(路由),navigateTo redirectTo reLaunch
  • 大型语言模型的中毒攻击的系统评价
  • 一款自制的OpenMV4模块
  • 网络原理8 - HTTP协议1
  • QDialog的show()方法与exec_()方法的区别详解
  • C#.Net 使用NPOI库导出Excel(含列宽度自适应) 及 根据Excel文件生成DataTable
  • 【多智能体】基于嵌套进化算法的多代理工作流
  • 硬件学习笔记--67 接线端子压缩比相关要求