1. 概述

模拟(Mocking)是单元测试中非常实用的功能,特别是在隔离测试方法中单一行为时。本教程将演示如何使用Mockito Spy模拟同一测试类中的方法。

2. 问题理解

单元测试是测试的第一道防线,能有效拦截早期bug。但有时方法过于复杂(尤其是遗留代码中),包含多个方法调用,且重构机会有限。我们将通过一个简单案例展示Mockito Spy的威力。

示例类CatTantrum

public class CatTantrum {
    public enum CatAction {
        BITE,
        MEOW,
        VOMIT_ON_CARPET,
        EAT_DOGS_FOOD,
        KNOCK_THING_OFF_TABLE
    }

    public enum HumanReaction {
        SCREAM,
        CRY,
        CLEAN,
        PET_ON_HEAD,
        BITE_BACK,
    }

    public HumanReaction whatIsHumanReaction(CatAction action){
        return switch (action) {
            case MEOW -> HumanReaction.PET_ON_HEAD;
            case VOMIT_ON_CARPET -> HumanReaction.CLEAN;
            case EAT_DOGS_FOOD -> HumanReaction.SCREAM;
            case KNOCK_THING_OFF_TABLE -> HumanReaction.CRY;
            case BITE -> biteCatBack();
        };
    }

    public HumanReaction biteCatBack() {
        // Some logic
        return HumanReaction.BITE_BACK;
    }
}

方法whatIsHumanReaction()包含业务逻辑并调用了biteCatBack()方法。我们将重点测试这个方法。

3. 使用Mockito Spy的解决方案

编写单元测试:

public void givenMockMethodHumanReactions_whenCatActionBite_thenHumanReactionsBiteBack(){
    // Given
    CatTantrum catTantrum = new CatTantrum();
    CatTantrum catTantrum1 = Mockito.spy(catTantrum);
    Mockito.doReturn(HumanReaction.BITE_BACK).when(catTantrum1).biteCatBack();

    // When
    HumanReaction humanReaction1 = catTantrum1.whatIsHumanReaction(CatAction.BITE);

    // Then
    assertEquals(humanReaction1, HumanReaction.BITE_BACK);
}

关键步骤解析:

  1. 创建spy对象Mockito.spy(catTantrum)允许我们模拟被调用方法biteCatBack()的返回值
  2. ⚠️ 调用测试方法:必须在spy对象catTantrum1上调用测试方法,而非原始对象
  3. 混合模式优势
    • 直接调用真实方法whatIsHumanReaction()
    • 同时模拟biteCatBack()的返回值

Mockito Spy的核心价值

  • 部分模拟:既可调用真实方法,又可存根特定方法
  • 避免全量存根:相比mock无需存根所有方法
  • 突破限制:相比真实对象能灵活模拟特定行为

4. 总结

本文展示了使用Mockito Spy解决同一测试类中方法模拟问题的方案。最佳实践是优先编写简洁、单一职责的方法,但当遇到复杂遗留代码时,Mockito Spy提供了简单粗暴的解决方案,能有效绕过测试障碍。


原始标题:Mocking a Method in the Same Test Class Using Mockito Spy | Baeldung