Mockito测试中服务层更新方法:正确模拟Repository行为

mockito测试中服务层更新方法:正确模拟repository行为

本文探讨了在M%ignore_a_1%ckito单元测试中,当服务层方法依赖于Repository的findById方法返回Optional时,可能遇到的UserNotFoundException问题。核心原因是findById方法未被正确模拟,导致Mockito默认返回Optional.empty()。文章提供了详细的解决方案,即通过when().thenReturn()明确模拟findById的返回值,并讨论了Mockito的默认行为、测试时序以及数据模拟的注意事项,旨在帮助开发者编写更健壮的服务层单元测试。

问题剖析:Optional.isEmpty()引发的异常

在开发基于Spring Boot等框架的应用时,服务层(Service Layer)通常会与数据访问层(Repository Layer)进行交互。当对服务层方法进行单元测试时,我们通常会使用Mockito等框架来模拟(Mock)Repository的行为,以隔离测试范围,确保只测试服务层自身的逻辑。

一个常见的问题场景是,当服务层中的更新方法(例如updateUser)首先通过Repository的findById方法查询现有数据,然后根据查询结果进行后续操作时,单元测试可能会失败并抛出UserNotFoundException。

考虑以下服务层updateUser方法的简化实现:

@Overridepublic UserDTO updateUser(String id, UserDTO updatedUser) {    // 1. 通过 updatedUser 的 userName 字段查找用户    Optional databaseUser = userRepository.findById(Integer.valueOf(updatedUser.getUserName()));    // 2. 如果未找到用户,则抛出异常    if (databaseUser.isEmpty()) {        throw new UserNotFoundException("User with the this id is not found");    }    // 3. 将更新后的 DTO 映射为实体    UserEntity entity = mapToUserEntity(updatedUser);    // 4. 保存更新后的实体    return map(userRepository.save(entity));}

在上述方法中,userRepository.findById()的返回值是一个Optional。如果这个Optional是空的(isEmpty()为true),则会抛出UserNotFoundException。

现在,让我们看看一个可能导致此问题的单元测试:

@Testvoid updateUserTest(){    final int id = 1;    final int userNameAsId = 12; // 假设 updatedUser.userName 会被解析为 12    UserDTO userDto = new UserDTO();    userDto.setUserName(String.valueOf(userNameAsId)); // 此值将用于 findById    // ... 其他 userDto 字段的设置 ...    // 模拟 roleRepository,与当前问题无关    when(roleRepository.findById(any())).thenReturn(Optional.of(new UserDTO().setId(2L)));    // 将 userDto 映射为 UserEntity,此实体将用于 save 方法    UserEntity userEntity = userService.mapToUserEntity(userDto);    // 模拟 userRepository.save 方法    when(userRepository.save(any())).thenReturn(userEntity.setId(id));    // 调用被测试的服务方法    userService.updateUser(String.valueOf(id), userDto); // 第一次调用    var actualUser = userService.updateUser(String.valueOf(id), userDto); // 第二次调用    // ... 断言 ...}

在这个测试中,问题在于 userRepository.findById() 方法没有被显式地模拟。当Mockito遇到一个未被模拟的方法调用时,它会使用其默认行为(RETURNS_DEFAULTS)。对于返回Optional类型的方法,Mockito的默认行为是返回一个空的Optional(即Optional.empty())。因此,当updateUser方法中的userRepository.findById(Integer.valueOf(updatedUser.getUserName()))被调用时,它将返回Optional.empty(),从而触发databaseUser.isEmpty()为true,最终导致UserNotFoundException被抛出,测试失败。

解决方案:正确模拟依赖行为

要解决这个问题,我们需要在调用被测试的服务方法之前,明确地模拟userRepository.findById()的行为,使其返回一个包含有效UserEntity的Optional。

以下是修正后的测试代码片段,突出显示了关键的模拟部分:

@Testvoid updateUserTestCorrected() {    final int id = 1;    final int userNameForLookup = 12; // 这个值是 updateUser 方法中 findById 的参数来源    UserDTO userDto = new UserDTO();    userDto.setUserName(String.valueOf(userNameForLookup)); // 确保 DTO 中的 userName 与查找逻辑匹配    userDto.setId(String.valueOf(id));    userDto.setName(new UserDTO.Name("surname", "firstname", "patronymic"));    userDto.setActive(true);    // ... 其他 userDto 字段的设置 ...    // 1. 模拟 userRepository.findById 方法    //    findById 会被调用参数为 userNameForLookup (即 12)    //    我们应该返回一个包含现有 UserEntity 的 Optional    UserEntity existingUserEntity = new UserEntity();    existingUserEntity.setId(userNameForLookup); // 设置一个 ID,表示这是数据库中已存在的用户    existingUserEntity.setName(new UserEntity.Name("oldSurname", "oldFirstname", "oldPatronymic"));    // ... 根据需要设置 existingUserEntity 的其他字段 ...    when(userRepository.findById(userNameForLookup)).thenReturn(Optional.of(existingUserEntity));    // 2. 模拟 userRepository.save 方法    //    这个实体是从 userDto 映射而来,是 save 方法的期望入参    UserEntity entityToSave = userService.mapToUserEntity(userDto);    entityToSave.setId(id); // 确保保存后返回的实体 ID 正确    when(userRepository.save(any(UserEntity.class))).thenReturn(entityToSave);    // 3. 调用被测试的服务方法    UserDTO actualUser = userService.updateUser(String.valueOf(id), userDto);    // 4. 断言验证    // 确保实际返回的用户数据与预期一致    // 注意:原始测试中对 userDto.setUserName(String.valueOf(id)); 的操作应在断言前完成,    // 或确保 userDto 在测试开始时就完全代表期望的最终状态。    // 这里我们直接比较 actualUser 和 userDto 的关键属性。    assertEquals(userDto.getUserName(), actualUser.getUserName());    assertEquals(userDto.getName().getFirstName(), actualUser.getName().getFirstName());    assertEquals(userDto.getId(), actualUser.getId());    // ... 更多详细断言 ...}

通过添加 when(userRepository.findById(userNameForLookup)).thenReturn(Optional.of(existingUserEntity)); 这一行,我们明确告诉Mockito:当userRepository.findById(12)被调用时,返回一个包含existingUserEntity的Optional,从而避免了Optional.empty()的默认行为,使服务层的isEmpty()检查通过。

进阶考量与最佳实践

Mockito默认行为的理解Mockito在没有明确模拟的情况下,对于不同返回类型的方法有不同的默认行为:

原始类型(如int, boolean): 返回其类型的默认值(0, false)。集合类型(如List, Set, Map): 返回空集合。Optional类型: 返回Optional.empty()。对象类型: 返回null。了解这些默认行为对于编写健壮的测试至关重要,它可以帮助我们预判哪些方法需要被显式模拟。

模拟的完整性与时序在单元测试中,模拟所有被测方法所依赖的外部协作对象(Mocks)的行为是关键。确保在调用被测方法(即测试的“执行”阶段)之前,所有相关的模拟都已设置完毕。例如,findById的模拟必须在userService.updateUser被调用之前完成。

模拟数据的匹配与上下文

findById的模拟: findById通常用于模拟从数据库中“读取”现有数据。因此,返回的UserEntity应该代表一个“已存在”的用户。它的ID应该与服务方法中用于查找的ID参数匹配。save的模拟: save方法通常用于模拟数据更新或插入。它的入参是服务层准备好的实体,返回的是“保存后”的实体(通常是同一个实体,可能带有数据库生成的ID)。在实际测试中,确保模拟返回的数据能够满足后续业务逻辑的需要,例如,如果后续逻辑会访问existingUserEntity的某个字段,那么模拟时就需要确保该字段有值。

测试断言的重要性一个完整的单元测试不仅要执行代码,更要验证其行为和结果。在updateUserTestCorrected示例中,我们不仅解决了UserNotFoundException,还增加了assertEquals来验证updateUser方法是否返回了预期的UserDTO,以及其内部的字段是否正确更新。这确保了我们不仅测试了代码的执行路径,还测试了其功能正确性。

总结

在Mockito单元测试中,当服务层方法依赖于Repository返回Optional类型的数据时,务必注意显式模拟findById等方法,以避免因Mockito默认返回Optional.empty()而导致的业务逻辑异常。通过清晰地模拟所有外部依赖的行为,并结合对Mockito默认行为的理解,我们可以编写出更稳定、更可靠的服务层单元测试,从而有效提升代码质量。

以上就是Mockito测试中服务层更新方法:正确模拟Repository行为的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/130938.html

赞 (0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
失眠的原因及对策(探索又困睡不着的背后原因与解决方法)
上一篇 2025年11月28日 15:03:15
拼多多账号资料修改失败怎么办 拼多多账号资料更新修复技巧
下一篇 2025年11月28日 15:03:24

相关推荐

  • edge提示“由你的组织管理”怎么解除_edge解除“由你的组织管理”提示的方法

    edge提示“由你的组织管理”怎么解除_edge解除“由你的组织管理”提示的方法edge提示“由你的组织管理”怎么解除_edge解除“由你的组织管理”提示的方法edge提示“由你的组织管理”怎么解除_edge解除“由你的组织管理”提示的方法edge提示“由你的组织管理”怎么解除_edge解除“由你的组织管理”提示的方法

    首先删除注册表中HKEY_LOCAL_MACHINE和HKEY_CURRENT_USER下的Edge策略项,再通过gpedit.msc禁用组策略设置,接着重置Edge浏览器,排查卸载第三方管理软件,最后手动清除Local State文件中的管理配置。 如果您在使用 Microsoft Edge 浏览…

    2026年9月27日 • 用户投稿
    100
  • 详解:如何将抖音订单接口接入到小程序上

    详解:如何将抖音订单接口接入到小程序上详解:如何将抖音订单接口接入到小程序上详解:如何将抖音订单接口接入到小程序上详解:如何将抖音订单接口接入到小程序上

    前言 作为全球领先的短视频平台,抖音拥有庞大的用户群体和活跃的电商生态。与此同时,小程序凭借其轻便、即用即走的特性,成为移动应用的重要形态之一。本文将深入讲解如何将抖音的订单接口成功接入小程序,助力开发者融合两大平台优势,提升服务体验与商业转化能力。 步骤一:理解抖音订单接口与小程序基础 在实际接入…

    2026年9月27日 • 用户投稿
    100
  • 选择最好用的文件同步工具(比较多种工具)

    选择最好用的文件同步工具(比较多种工具)选择最好用的文件同步工具(比较多种工具)选择最好用的文件同步工具(比较多种工具)选择最好用的文件同步工具(比较多种工具)

    在数位时代,文件同步工具已不可或缺,但面对市面上众多选择,如何找到最适合自己需求的解决方案?php小编小新为您深入比较了市面上主流的文件同步工具,涵盖其功能、优点和缺点。通过本文,您将了解到:不同文件同步工具的主要差异是什么?每种工具提供的独有功能和优势是什么?哪种工具最适合您的特定需求?阅读本文,…

    2026年9月27日 • 用户投稿
    100
  • Maven进阶实战:多模块项目依赖管理与冲突解决

    Maven进阶实战:多模块项目依赖管理与冲突解决Maven进阶实战:多模块项目依赖管理与冲突解决Maven进阶实战:多模块项目依赖管理与冲突解决Maven进阶实战:多模块项目依赖管理与冲突解决

    答案:Maven多模块项目依赖管理核心在于父POM中使用统一版本、合理划分模块实现高内聚低耦合、通过排除冲突传递依赖,并利用mvn dependency:tree等工具分析依赖树,结合BOM引入、版本属性化管理等策略,确保依赖一致性与项目可维护性。 Maven多模块项目中的依赖管理与冲突解决,核心在…

    2026年9月26日 • 用户投稿
    300
  • DDR5内存超频指南:芝奇幻锋戟6400MHz稳定性测试

    DDR5内存超频指南:芝奇幻锋戟6400MHz稳定性测试DDR5内存超频指南:芝奇幻锋戟6400MHz稳定性测试DDR5内存超频指南:芝奇幻锋戟6400MHz稳定性测试DDR5内存超频指南:芝奇幻锋戟6400MHz稳定性测试

    ddr5内存可以超频到6400mhz,但需要仔细调整和测试以确保稳定性。1.选择适合超频的ddr5内存条时,优先考虑芝奇幻锋戟系列,选择标有“oc”标签且规格接近目标频率的产品。2.在bios中将频率设为6400mhz,从宽松时序如cl36-36-36-76开始调整,并设定电压在1.25v到1.35…

    2026年9月26日 • 用户投稿
    000
  • 多核处理器在运行虚拟机时有哪些优势?

    多核处理器在运行虚拟机时有哪些优势?多核处理器在运行虚拟机时有哪些优势?多核处理器在运行虚拟机时有哪些优势?多核处理器在运行虚拟机时有哪些优势?

    多核处理器通过提升并行处理能力使虚拟机运行更流畅,核心越多,可分配资源越多,减少上下文切换,提高并发效率,配合内存、存储、网络等优化,整体性能显著增强。 多核处理器让虚拟机运行更流畅,简单说,就是能同时处理更多任务,避免卡顿。虚拟机就像电脑里的“套娃”,每个都需要资源,核越多,分到的资源就多,自然跑…

    2026年9月26日 • 用户投稿
    200
  • windows卡在欢迎界面怎么办_Windows卡在欢迎界面无法进入系统解决方法

    windows卡在欢迎界面怎么办_Windows卡在欢迎界面无法进入系统解决方法windows卡在欢迎界面怎么办_Windows卡在欢迎界面无法进入系统解决方法windows卡在欢迎界面怎么办_Windows卡在欢迎界面无法进入系统解决方法windows卡在欢迎界面怎么办_Windows卡在欢迎界面无法进入系统解决方法

    强制重启进入安全模式排查问题,确认是否因第三方程序导致;2. 创建新用户账户以绕过损坏的配置文件;3. 禁用非必要启动项和服务释放系统资源;4. 使用sfc和DISM命令修复系统文件与镜像;5. 卸载最近更新或驱动解决兼容性问题,最终恢复系统正常登录。 如果您在启动Windows系统时,发现设备长时…

    2026年9月26日 • 用户投稿
    300
  • DeepSeek如何接入本地数据库 数据对接的配置方式与使用注意事项

    DeepSeek如何接入本地数据库 数据对接的配置方式与使用注意事项DeepSeek如何接入本地数据库 数据对接的配置方式与使用注意事项DeepSeek如何接入本地数据库 数据对接的配置方式与使用注意事项DeepSeek如何接入本地数据库 数据对接的配置方式与使用注意事项

    本文旨在介绍如何实现将数据对接至本地数据库,供使用DeepSeek或其他类似模型处理的应用程序进行访问。我们将概述整个过程,包括前期准备工作、详细的配置步骤以及在使用过程中需要注意的重要事项。通过阅读本文,您将了解从环境搭建到数据访问的核心环节,从而能够顺利地将您的本地数据与基于DeepSeek的应…

    2026年9月25日 • 用户投稿
    800
  • 从文件解析游戏物品数据:Java实现教程

    从文件解析游戏物品数据:Java实现教程从文件解析游戏物品数据:Java实现教程从文件解析游戏物品数据:Java实现教程从文件解析游戏物品数据:Java实现教程

    本教程详细介绍了如何从文本文件中解析结构化的游戏物品数据,例如itemName:(“Steel Sword”),itemStats(2,0,0);。通过构建一个Item类来封装物品属性,并利用Java的文件I/O和字符串处理功能,实现数据的读取、解析和存储到对象列表中,从而为…

    2026年9月25日 • 用户投稿
    000
  • 利用Laravel高效串联查询:从上一个结果获取数据

    本教程旨在解决laravel中基于前一个查询结果进行后续查询的常见问题。文章详细阐述了如何避免因`take(1)->toarray()`导致的多维数组问题,并优化了查询效率,通过使用`first()`方法获取单个记录,并直接在数据库层面进行过滤,而非在内存中处理大量数据,从而提升应用性能和代码…

    2026年9月24日
    700
  • TradingAgents-CN— 中文多智能体金融交易决策框架

    TradingAgents-CN— 中文多智能体金融交易决策框架TradingAgents-CN— 中文多智能体金融交易决策框架TradingAgents-CN— 中文多智能体金融交易决策框架TradingAgents-CN— 中文多智能体金融交易决策框架

    TradingAgents-CN是什么 tradingagents-cn是基于多智能体大模型的中文金融交易决策框架,在tauricresearch/tradingagents的基础上进行了开发,为中文用户提供了完整的文档体系和本地化支持。框架模拟真实交易公司的专业分工和协作决策流程,通过多个专业化a…

    2026年9月24日 • 用户投稿
    900
  • laravel怎么在模型中定义远程一对一或一对多关系_laravel模型远程关联定义方法

    使用 hasManyThrough 和 hasOneThrough 可在 Laravel 中实现通过中间模型访问远端数据,需确保外键正确或自定义键名以维持关联完整性。 如果您需要在 Laravel 模型中访问通过中间模型关联的远端数据,但两个模型之间没有直接关系,而是通过第三个模型连接,则可以使用“…

    2026年9月24日
    000
  • 数据库设计原则?——规范化理论

    数据库设计原则?——规范化理论数据库设计原则?——规范化理论数据库设计原则?——规范化理论数据库设计原则?——规范化理论

    数据库设计的规范化理论旨在减少冗余、提升一致性与完整性,核心是通过1nf、2nf、3nf三级范式逐步消除数据异常。1nf要求字段具有原子性,不可再分;2nf要求非主键字段完全依赖主键,而非部分依赖;3nf进一步消除传递依赖,确保非主键字段不依赖其他非主键字段。规范化虽能提高数据可靠性,但可能导致查询…

    2026年9月24日 • 用户投稿
    000
  • VSCode如何配置.NET开发环境 VSCode搭建.NET项目的完整流程

    首先安装.net sdk并验证版本;2. 安装vscode及microsoft官方c#扩展,确保智能感知和调试功能正常;3. 通过dotnet new命令创建项目,并使用code .在vscode中打开项目;4. 添加构建和调试资产以生成tasks.json和launch.json文件;5. 安装n…

    2026年9月24日
    100
  • Java类间ArrayList访问:解决“无法解析方法”的包冲突问题

    本文旨在解决Java开发中,一个类(如Bill)无法访问另一个类(如自定义Menu)中ArrayList的常见问题。核心原因通常是包冲突,即系统默认导入的同名类(如java.awt.Menu)覆盖了自定义类。解决方案包括为自定义类声明明确的包,并在使用时进行显式导入,或确保两者位于同一默认包中,从而…

    2026年9月23日
    100
  • Java类间访问:解决“无法解析方法”的包管理与导入策略

    本文旨在解决Java开发中常见的跨类数据访问问题,特别是当自定义类与标准库类存在名称冲突时导致的“无法解析方法”错误。我们将通过详细阐述Java包的机制,提供两种解决方案:推荐的包导入方式和在默认包中处理的简单方法,以确保不同类之间能够正确地进行交互和数据共享,从而提升代码的可维护性和健壮性。 引言…

    2026年9月23日
    300
  • 解决TCPDF保存文件权限问题的完整指南

    本文旨在解决使用tcpdf在%ignore_a_1%中生成pdf并保存到服务器(’f’模式)时遇到的“permission denied”错误,尤其是在macos环境下。核心问题通常源于不正确的服务器文件路径或目标文件夹缺乏写入权限。教程将详细阐述如何构建正确的绝对文件路径,…

    2026年9月22日
    300
  • GPU显存时序修改(Timing Tuning)的风险与性能收益

    显存时序调校可提升性能但伴随风险。通过优化时序能降低延迟、提高带宽利用率,增强游戏帧率并配合超频发挥更好效果;但激进设置易引发系统崩溃、花屏、蓝屏等问题,长期不稳定运行还可能损伤硬件,导致保修失效。建议仅限进阶用户在充分准备下使用专业工具小幅调整,并进行严格稳定性测试,普通用户应保持默认设置以确保安…

    2026年9月22日
    200
  • Spring Boot 应用中的单元测试、Mockito 和集成测试:最佳实践

    第一段引用上面的摘要: 本文旨在帮助初学者理解在 Spring Boot 应用中何时以及如何使用 JUnit、Mockito 和集成测试。我们将探讨这些测试框架在 Controller、Service 和 Repository 层中的应用,并提供示例说明何时使用 Mockito 模拟对象,以及何时使…

    2026年9月22日
    200
  • 如何在DaVinciResolve中制作AI视频?教你利用AI工具优化视频流程

    如何在DaVinciResolve中制作AI视频?教你利用AI工具优化视频流程如何在DaVinciResolve中制作AI视频?教你利用AI工具优化视频流程如何在DaVinciResolve中制作AI视频?教你利用AI工具优化视频流程如何在DaVinciResolve中制作AI视频?教你利用AI工具优化视频流程

    达芬奇Resolve并非一键生成AI视频的%ignore_a_1%,而是通过内置AI功能与外部AI服务协同,提升视频制作效率。其核心在于利用Neural Engine驱动的智能工具,如Magic Mask实现精准抠像、Voice Isolation分离人声、Smart Reframe适配多平台构图、…

    2026年9月22日 • 用户投稿
    900

发表回复

登录后才能评论
关注微信