展开菜单
首页 精品内容 本月促销 装机必备 Windows PDF教程 macOS软件 IOS软件 Android AI 专题
全部分类

当前位置:

首页 > 编程开发 > Mockito验证失败与内部依赖模拟解析

Mockito验证失败与内部依赖模拟解析

本文深入探讨了Mockito单元测试中verify失败的常见原因,特别是当被测对象在内部创建其依赖时。文章详细介绍了如何利用Mockito的spy功能结合doReturn().when()来模拟这些内部创建的依赖,确保测试的正确性。同时,还涵盖了MockitoAnnotations.initMocks的替代方案、spy与InjectMocks的区别,以及PowerMock的使用场景,旨在提供一套全面的Java单元测试最佳实践。

本文目录

    Java单元测试中Mockito Verify失败与内部依赖模拟解析

    本文深入探讨了Mockito单元测试中verify失败的常见原因,特别是当被测对象在内部创建其依赖时。文章详细介绍了如何利用Mockito的spy功能结合doReturn().when()来模拟这些内部创建的依赖,确保测试的正确性。同时,还涵盖了MockitoAnnotations.initMocks的替代方案、spy与InjectMocks的区别,以及PowerMock的使用场景,旨在提供一套全面的Java单元测试最佳实践。

    1. 理解测试失败的根本原因:内部依赖创建

    在单元测试中,我们通常希望隔离被测单元(System Under Test, SUT),只关注其自身的逻辑,而将其依赖项替换为模拟对象(Mock)。然而,当被测类(如示例中的Thing类)在执行其业务逻辑时,不是通过构造函数或Setter方法接收依赖,而是在其内部方法中直接创建这些依赖(如Thing的fun()方法内部创建ThingGrandParent,ThingGrandParent的GpFun()方法内部创建ThingParent),就会导致verify失败。

    原始测试代码的问题在于:

    1. Thing t = spy(new Thing()); 创建了一个Thing的间谍对象。
    2. when(tgpp.GpFun(anyInt())).thenReturn(tp); 设定了tgpp模拟对象的行为。
    3. 当t.bLogic(4)被调用时,它会执行ThingGrandParent tgp = fun(size);。这里的fun(size)是Thing类自己的方法,它会执行return new ThingGrandParent(size);,从而创建了一个全新的ThingGrandParent实例,而不是我们期望的tgpp模拟对象。
    4. 因此,后续的操作都是在这个真实创建的ThingGrandParent实例及其子依赖上进行的,我们的tgpp和tp模拟对象从未被实际调用,导致verify(tp, times(1)).fetchSideThing()); 失败,并抛出“Wanted but not invoked”的错误。

    简而言之,问题出在依赖的创建方式上:被测对象没有使用我们提供的模拟依赖,而是自行创建了真实的依赖。

    2. 使用Mockito Spy和Mock解决内部依赖问题

    为了解决上述问题,我们需要确保被测对象Thing在执行其业务逻辑时,能够使用我们预设的模拟对象。由于Thing内部通过fun()方法创建了ThingGrandParent,我们可以对Thing的fun()方法进行模拟,使其返回我们准备好的ThingGrandParent模拟对象。

    以下是修正后的测试代码示例:

    import org.junit.Before;
    import org.junit.Rule;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.mockito.Mock;
    import org.mockito.junit.MockitoJUnit;
    import org.mockito.junit.MockitoRule;
    import org.powermock.core.classloader.annotations.PowerMockIgnore;
    import org.powermock.core.classloader.annotations.PrepareForTest;
    import org.powermock.modules.junit4.PowerMockRunner;
    
    import static org.junit.Assert.assertEquals;
    import static org.mockito.ArgumentMatchers.anyInt;
    import static org.mockito.Mockito.doReturn;
    import static org.mockito.Mockito.spy;
    import static org.mockito.Mockito.times;
    import static org.mockito.Mockito.verify;
    import static org.mockito.Mockito.when;
    
    // 示例类定义 (与原文保持一致,此处省略以聚焦测试代码)
    // @Getter @Setter public class SideThing { ... }
    // public class ThingGrandParent { ... }
    // public class ThingParent { ... }
    // @Getter @Setter public class Thing { ... }
    
    @RunWith(PowerMockRunner.class) // 如果需要PowerMock的其他高级功能,则保留
    @PowerMockIgnore("javax.management.*")
    // 准备所有涉及模拟或间谍的类,特别是那些最终的、静态的或构造函数被模拟的类
    @PrepareForTest({Thing.class, SideThing.class, ThingGrandParent.class, ThingParent.class})
    public class ThingTest {
    
        @Mock
        ThingParent mockThingParent; // 模拟ThingParent
        @Mock
        ThingGrandParent mockThingGrandParent; // 模拟ThingGrandParent
        @Mock
        SideThing mockSideThing; // 模拟SideThing
    
        Thing spyThing; // Thing的间谍对象
    
        // 使用MockitoRule替代 deprecated 的 MockitoAnnotations.initMocks
        @Rule
        public MockitoRule rule = MockitoJUnit.rule();
    
        @Before
        public void setup() {
            // 创建Thing的间谍对象,这样可以调用真实方法,也可以模拟特定方法
            spyThing = spy(new Thing());
            // 不需要 MockitoAnnotations.initMocks(this);
        }
    
        @Test
        public void testLogicWithMockedDependencies() throws Exception {
            int expectedWeight = 4;
            int inputSize = 4;
    
            // 1. 模拟 spyThing.fun() 方法的行为
            //    当 spyThing 的 fun() 方法被调用时,返回我们的 mockThingGrandParent 模拟对象。
            //    使用 doReturn().when() 对 spy 对象进行方法模拟,这比 when().thenReturn() 更安全。
            doReturn(mockThingGrandParent).when(spyThing).fun(anyInt());
    
            // 2. 模拟 mockThingGrandParent.GpFun() 方法的行为
            //    当 mockThingGrandParent 的 GpFun() 方法被调用时,返回我们的 mockThingParent 模拟对象。
            doReturn(mockThingParent).when(mockThingGrandParent).GpFun(anyInt());
    
            // 3. 模拟 mockThingParent.fetchSideThing() 方法的行为
            //    当 mockThingParent 的 fetchSideThing() 方法被调用时,返回我们的 mockSideThing 模拟对象。
            doReturn(mockSideThing).when(mockThingParent).fetchSideThing();
    
            // 4. 模拟 mockSideThing.getWeight() 方法的行为
            //    当 mockSideThing 的 getWeight() 方法被调用时,返回预期的权重值。
            when(mockSideThing.getWeight()).thenReturn(expectedWeight);
    
            // 执行被测方法
            int result = spyThing.bLogic(inputSize);
    
            // 验证结果
            assertEquals(expectedWeight, result);
    
            // 验证方法调用
            // 验证 spyThing 的 fun 方法被调用了一次,参数为 inputSize
            verify(spyThing, times(1)).fun(inputSize);
            // 验证 mockThingGrandParent 的 GpFun 方法被调用了一次,参数为 inputSize
            verify(mockThingGrandParent, times(1)).GpFun(inputSize);
            // 验证 mockThingParent 的 fetchSideThing 方法被调用了一次
            verify(mockThingParent, times(1)).fetchSideThing();
            // 验证 mockSideThing 的 getWeight 方法被调用了一次
            verify(mockSideThing, times(1)).getWeight();
        }
    }

    代码解析:

    • @Mock 声明模拟对象: 我们声明了mockThingParent、mockThingGrandParent和mockSideThing作为模拟对象。
    • spy(new Thing()) 创建间谍对象: spyThing是一个真实的Thing对象,但我们可以监控其方法调用,并选择性地模拟其某些方法的行为。
    • doReturn(mockThingGrandParent).when(spyThing).fun(anyInt());: 这是解决核心问题的关键。它告诉Mockito:当spyThing对象的fun()方法被调用时,不要执行其真实逻辑(即new ThingGrandParent(size)),而是直接返回我们预设的mockThingGrandParent模拟对象。这样,bLogic方法后续对tgp的操作就都会作用在我们的模拟对象上了。
    • 链式模拟: 接着,我们按照业务逻辑的调用顺序,模拟了mockThingGrandParent.GpFun()返回mockThingParent,以及mockThingParent.fetchSideThing()返回mockSideThing,最后模拟mockSideThing.getWeight()返回期望值。
    • verify 验证: 在执行完业务逻辑后,我们验证了所有关键方法的调用次数和参数,确保了业务流程的正确性。

    3. Mockito注解初始化最佳实践

    MockitoAnnotations.initMocks(this); 方法在Mockito 2.x版本后已被标记为弃用。对于JUnit 4和JUnit 5,有更现代和推荐的替代方案:

    • JUnit 4 (@Rule): 使用MockitoJUnit.rule()结合@Rule注解。这会自动初始化所有带有@Mock、@Spy或@InjectMocks注解的字段。

      import org.junit.Rule;
      import org.mockito.junit.MockitoJUnit;
      import org.mockito.junit.MockitoRule;
      
      public class MyTest {
          @Rule
          public MockitoRule rule = MockitoJUnit.rule();
      
          @Mock
          MyDependency mockDependency;
          // ...
      }
    • JUnit 5 (@ExtendWith): 使用@ExtendWith(MockitoExtension.class)注解。这是JUnit 5中扩展机制的一部分。

      import org.junit.jupiter.api.extension.ExtendWith;
      import org.mockito.Mock;
      import org.mockito.junit.jupiter.MockitoExtension;
      
      @ExtendWith(MockitoExtension.class)
      public class MyTest {
          @Mock
          MyDependency mockDependency;
          // ...
      }

      在示例代码中,我们采用了JUnit 4的@Rule方式。

    4. Spy与InjectMocks的选择与应用场景

    @Spy和@InjectMocks都是Mockito中用于处理真实对象与模拟对象结合的注解,但它们的应用场景和目的不同。

    • @Spy (或 Mockito.spy()):
      • 目的: 对一个真实对象进行部分模拟。这意味着该对象的大部分方法会执行其真实逻辑,但你可以选择性地模拟其中一小部分方法。
      • 何时使用: 当你需要测试一个真实对象,但该对象内部调用的某些方法(可能是它自己的私有方法,或它内部创建的依赖对象的方法)行为不稳定、耗时或你希望隔离时。在我们的例子中,Thing是一个真实对象,但我们希望模拟它内部调用的fun()方法。
      • 特点: spy对象仍然是一个真实对象,其
    本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
    作者最新文章
    编程开发
    相关文章 更多
    精品专题 更多
    装机必备

    正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

    Windows

    正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

    PDF教程

    正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

    macOS软件

    正软商城macOS软件专区,精选适用于Mac电脑的办公、设计、影音、效率、开发和系统工具,提供软件功能介绍、macOS兼容版本、正版授权及购买下载服务。

    IOS软件

    正软商城iOS软件专区,精选适用于iPhone和iPad的办公、学习、影音、设计、效率及AI应用,提供功能介绍、适用设备、系统要求和正版获取方式等信息。

    AI

    正软商城AI软件专区,汇集AI写作、AI绘画、AI视频、AI办公、AI编程、AI翻译、智能客服和数据分析等人工智能工具,提供功能介绍、适用平台、收费方式及正版购买信息。

    Mac软件 更多
    讯飞智作 网页版
    讯飞智作 网页版

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    Aperty macOS版
    Aperty macOS版

    Aperty 是 Skylum 推出的 AI 人像修图软件,支持自然肤质修饰、瑕疵处理、面部与身体调整、预设及批量同步。

    立刻MV 网页版
    立刻MV 网页版

    立刻MV 面向音乐人、内容创作者和品牌团队,帮助用户低成本完成歌曲 MV、歌词视频、AI 分镜、图生视频与最终合成。

    蛙蛙写作 网页版
    蛙蛙写作 网页版

    蛙蛙写作 AI 创作工具,覆盖小说、剧本、文案,一键生成文稿,打通文字转视频全流程,高效解决创作难题。

    超级简历WonderCV 网页版
    超级简历WonderCV 网页版

    超级简历WonderCV是面向应届生和AI小白的智能求职平台,覆盖简历、面试与职业规划。

    音述AI 网页版
    音述AI 网页版

    音述AI,是一个以“用声音述说故事”为核心的 AI 音乐创作与声音分享社区。

    AionClaw macOS版
    AionClaw macOS版

    AionClaw 是可在桌面端运行的任务型 AI 助手,通过数字专家与技能市场组合办公、创作、数据和开发能力。

    Loomy macOS版
    Loomy macOS版

    Loomy 是科大讯飞推出的桌面 AI 助手,可通过对话、任务与工作目录协助处理办公、内容创作和资料整理。

    AirBuddy 3 macOS版
    AirBuddy 3 macOS版

    AirBuddy 3 是面向 macOS 的蓝牙外设增强工具,可集中查看 AirPods、Beats 与苹果设备电量,提供快速连接、播放控制、电量提醒、桌面小组件和多台 Mac 外设接力等功能。

    AirServer macOS版
    AirServer macOS版

    AirServer macOS版可把 Mac 转换为无线投屏接收端,通过 AirPlay 或 Google Cast 接收手机、平板和电脑画面,并支持多设备同时连接、高清镜像及会话录制,适合家庭娱乐、课堂教学、会议演示和应用展示。

    AnyDesk macOS版
    AnyDesk macOS版

    AnyDesk macOS版是一款适用于远程办公、设备维护和客户支持的远程桌面工具,可连接并操作异地计算机,支持文件传输、无人值守访问、会话权限控制及跨平台协作。

    WINDOWS 更多
    讯飞智作 网页版
    讯飞智作 网页版

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    PixTV 网页版
    PixTV 网页版

    PixTV 是一款在浏览器中使用的 AI 视觉内容创作平台,提供图片、视频、音频、文字生成、AI 分镜、局部重绘、提示词反推和无限画布等工具,适合短剧、漫剧、广告及社交媒体内容制作。

    讯飞绘文 网页版
    讯飞绘文 网页版

    讯飞绘文网页版是科大讯飞推出的AI图文创作与内容运营工具,覆盖热点选题、文章生成、稿件校对、智能配图、排版发布和数据复盘等环节,适合自媒体创作者及内容团队在线使用。

    Maya(Autodesk Maya) Windows版
    Maya(Autodesk Maya) Windows版

    Autodesk Maya 是面向影视、电视、动画和游戏行业的专业三维制作软件,集多边形与 NURBS 建模、角色绑定、关键帧和动作捕捉动画、Bifrost 特效模拟、材质开发及 Arnold 渲染于一体,

    Aperty Windows版
    Aperty Windows版

    Aperty 是 Skylum 推出的 AI 人像修图软件,支持自然肤质修饰、瑕疵处理、面部与身体调整、预设及批量同步。

    立刻MV 网页版
    立刻MV 网页版

    立刻MV 面向音乐人、内容创作者和品牌团队,帮助用户低成本完成歌曲 MV、歌词视频、AI 分镜、图生视频与最终合成。

    蛙蛙写作 网页版
    蛙蛙写作 网页版

    蛙蛙写作 AI 创作工具,覆盖小说、剧本、文案,一键生成文稿,打通文字转视频全流程,高效解决创作难题。

    超级简历WonderCV 网页版
    超级简历WonderCV 网页版

    超级简历WonderCV是面向应届生和AI小白的智能求职平台,覆盖简历、面试与职业规划。

    音述AI 网页版
    音述AI 网页版

    音述AI,是一个以“用声音述说故事”为核心的 AI 音乐创作与声音分享社区。

    AionClaw Windows版
    AionClaw Windows版

    AionClaw 是可在桌面端运行的任务型 AI 助手,通过数字专家与技能市场组合办公、创作、数据和开发能力。

    Loomy Windows版
    Loomy Windows版

    Loomy 是科大讯飞推出的桌面 AI 助手,可通过对话、任务与工作目录协助处理办公、内容创作和资料整理。