【问题标题】:How to mock methods called by method under the test如何模拟被测方法调用的方法
【发布时间】:2016-12-05 11:18:11
【问题描述】:

我正在使用遗留代码,并希望增加它的测试覆盖率。

我有这样的课:

public class MyStrategy implements SomeStrategy {

    TimeAndDateUtils timeAndDateUtils = new TimeAndDateUtils();

    @Override
    public boolean shouldBeExtracted(Argument argument) {
        Date currentDate = timeAndDateUtils.getCurrentDate();
        return currentDate.isBefore(argument.getDate());
    }

}

我想测试 shouldBeExtracted-Method 并模拟对 timeAndDateUtils.getCurrentDate() 的调用,以便它返回一些固定值。

所以我想做的是:

Date currentDate = %some fixed date%
TimeAndDateUtils timeAndDateUtils = Mockito.mock(TimeAndDateUtils.class);
Mockito.when(timeAndDateUtils.getCurrentDate()).thenReturn(currentDate);
Assert.assertTrue(myStrategy.shouldBeExtracted(argument))

如何强制 MyStrategy-Class 使用模拟对象而不是创建自己的对象?

【问题讨论】:

    标签: java unit-testing mocking mockito


    【解决方案1】:

    您可以使用反射将模拟放入 MyStrategy 对象。它看起来像这样:

    MyStrategy myStrategy = new MyStrategy(); // I don't know if you are using DI
    MyStrategy.class.getDeclaredField("timeAndDateUtils").set(myStrategy, timeAndDateUtilsMock);
    

    【讨论】:

    • 感谢您的建议!我发现@kimi82 的解决方案更优雅一些。
    【解决方案2】:

    假设您无法重写现有代码以使其更具可测试性,这是注解 @InjectMocks 的典型用例,允许自动将模拟或间谍字段注入测试对象。 p>

    @RunWith(MockitoJUnitRunner.class)
    public class MyStrategyTest {
        @Mock
        private TimeAndDateUtils timeAndDateUtils;
    
        @InjectMocks
        private MyStrategy myStrategy;
    
        @Test
        public void testShouldBeExtracted() {
            ...
            Mockito.when(timeAndDateUtils.getCurrentDate()).thenReturn(currentDate);
            Assert.assertTrue(myStrategy.shouldBeExtracted(argument));
        }
    }
    

    【讨论】:

    • 这是一个没有任何 DI 的旧 Java SE 应用程序
    • 你好@Nicolas,我使用的是 Mockito 1.9.x。我在其他项目中广泛使用 InjectMocks。即使在询问堆栈溢出之前,我也已经在这个中尝试过。由于某些原因它不起作用,所以我认为这与该项目中绝对缺乏 DI 有关。但是由于您的解释,它实际上应该有效。我再试一次。谢谢,伊琳娜。
    • 我刚刚用 Mockito 1.9.5 测试了完全相同的代码,它运行良好
    • 确保用@RunWith(MockitoJUnitRunner.class)注释你的Test类
    • Hello @Nicolas 在用 @InjectMocks 注释 myStrategy 后,我不能再模拟方法调用了。然后我收到一条错误消息:when() requires an argument which has to be 'a method call on a mock'.
    【解决方案3】:

    Mockito 有一个很好的类来覆盖类中的私有对象。它被称为白盒。它是这样工作的

    MyStrategy myStrategy = new MyStrategy();
    TimeAndDateUtils timeAndDateUtils = Mockito.mock(TimeAndDateUtils.class);
    Whitebox.setInternalState(myStartegy, "timeAndDateUtils", timeAndDateUtilsMock);
    

    这将为您的模拟更改您的 TimeAndDateUtils

    【讨论】:

    • 虽然这可行,但它是一个糟糕的解决方案。相反,要测试的方法可以很容易地被两个简单的测试覆盖,传递一个包含当前日期之前/之后的日期的Argument。无需嘲笑。
    • 这样做是在同一个测试中测试两段遗留代码。除非 TimeAndDateUtils 是已知框架/库的一部分,否则我不会依赖 TimeAndDateUtils 类,我会为两个不相互依赖的类创建测试。
    • 嗯,十年的经验(以及创建的数千个测试)告诉我集成测试总是比孤立的单元测试更好。
    • TDD 和 BDD 都有助于确保质量
    • 从未使用过 BDD,只使用过 TDD。 (BDD 要求在非技术利益相关者的直接参与下创建测试,这在实践中几乎是不可能的。)
    【解决方案4】:

    timeAndDateUtils 是在类本身中创建的,因此难以访问以进行测试。通过构造函数注入依赖,这样就可以创建一个 mock

    public class MyStrategy implements SomeStrategy {
    
        TimeAndDateUtils timeAndDateUtils;
    
        public MyStrategy(TimeAndDateUtils timeAndDateUtils) {
            this.timeAndDateUtils = timeAndDateUtils;
        }
    
        @Override
        public boolean shouldBeExtracted(Argument argument) {
            Date currentDate = timeAndDateUtils.getCurrentDate();
            return currentDate.isBefore(argument.getDate());
        }
    
    }
    

    测试

    //Arrange
    Date currentDate = %some fixed date%
    TimeAndDateUtils timeAndDateUtils = Mockito.mock(TimeAndDateUtils.class);
    Mockito.when(timeAndDateUtils.getCurrentDate()).thenReturn(currentDate);
    
    MyStrategy myStrategy = new MyStrategy(timeAndDateUtils);
    
    //Act 
    bool result = myStrategy.shouldBeExtracted(argument);
    
    //Assert
    Assert.assertTrue(result);
    

    【讨论】:

    • 该类是通用接口“SomeStrategy”的实现之一。我不喜欢只为其中一个更改构造函数。它使代码的可读性更差。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多