JMockit模拟System.currentTimeMillis()

3

运行这个测试:

@Test
public void testSystemCurrentTimeMillis() {
    new NonStrictExpectations(System.class) {{
        System.currentTimeMillis(); result = 1438357206679L;
    }};
    long currentTime = System.currentTimeMillis();
    assertEquals(1438357206679L, currentTime);
}

我遇到了一个IllegalStateException错误:

java.lang.IllegalStateException: 在此时缺少对模拟类型的调用;请确保此类调用仅在适当的模拟字段或参数声明之后出现
    at unittests.DateTest$1.(DateTest.java:24)
    at unittests.DateTest.testSystemCurrentTimeMillis(DateTest.java:23)

我的测试有什么问题(使用JMockit 1.18)?


结果是什么?你能分享整个类的代码吗?另外,NonStrictExpectations是什么?你也能分享一下它的代码吗? - StackFlowed
NonStrictExpectations是JMockit类。result用于JMockit模拟。http://jmockit.org/tutorial/Mocking.html#expectation - trunkc
我总是使用org.joda.time.DateTimeUtils来获取当前时间DateTimeUtils.currentTimeMillis(),在单元测试中使用DateTimeUtils.setCurrentMillisFixed(longValue),最后使用DateTimeUtils.setCurrentMillisSystem()。 - StackFlowed
请参考以下链接:https://dev59.com/K2Qm5IYBdhLWcg3w8Sne - Raedwald
我想用JMockit来完成它。 - trunkc
你为什么要测试System.currentTimeMillis()的值? - forty-two
5个回答

3
我的最终解决方案是为系统创建一个MockUp,仅模拟了currentTimeMillis()方法:
private static class SystemMock extends MockUp<System> {
    @Mock
    public static long currentTimeMillis() {
        return 10000000L;
    }
}

@Test
public void testAddNowDate() {
    new SystemMock();
    long currentTime = System.currentTimeMillis();
    assertEquals(10000000L, currentTime);
}

1
是的,在这种情况下,这是最好的解决方案。话虽如此,您应该尽可能避免模拟系统时钟;在测试中需要模拟它暗示着被测试代码中存在设计问题。 - Rogério
@Rogério 能否详细说明一下?如果没有模拟系统时钟,我该如何测试依赖于系统时钟的功能呢? - KidCrippler
需要当前时间的代码应该使用更高级别的类型(java.util.Datejava.time.LocalDate等),而不是currentTimeMillis()。然后它应该允许从客户端代码传递日期/时间对象,而不总是获取当前时间。当然,除非你真的需要毫秒/纳秒的当前系统时钟时间。正如我所说,调用System.currentTimeMillis()可能是一个设计问题,但如果没有更多信息,我无法确定。 - Rogério
一个使用场景是制作模拟测试用例,以使用currentTimeMillis或nanoTime检查经过的时间。如果进行模拟,则可以检查边缘情况。我尝试过,但在我的情况下变得太复杂了...出现了太多OOM错误等问题。 - Jordan Gee
1
是的!互联网上所有的内容都说在模拟 System 时存在设计问题,但这就好像在说我们永远不应该使用 System 一样,这并不是真的。有时确实存在设计问题。有时没有任何方法可以改变所述设计。有时你只需要帮助模拟 System,不管它的设计如何。 - John Mercier

2

就像JMockit的许多功能一样,这也很容易实现。尝试以下代码:

@Test
public void testSystemCurrentTimeMillis(@Mocked final System unused) {
    new NonStrictExpectations() {{
        System.currentTimeMillis(); result = 1438357206679L;
    }};
    long currentTime = System.currentTimeMillis();
    assertEquals(1438357206679L, currentTime);
}

顺便提一下,我发现这个网站是一个很好的参考资料。也许你被静态方法绊住了。你需要做的就是将包含静态方法的类声明为模拟类--你永远不需要引用变量,因此我将其命名为“未使用”。


已接受您的答案,但我只想模拟currentTimeMillis()方法,而不是整个System类。看来这是JMockit中的一个bug。 - trunkc
1
这不是一个 bug,而是“部分模拟”功能的限制:它无法处理像 System.currentTimeMillis() 这样的“native”方法。然而,这个限制仅适用于 Expectations API;你可以通过创建 MockUp<System>(使用 Mockups API)来模拟所需的 “native” 方法。 - Rogério
你应该在教程中添加关于这个限制的提示。 - trunkc

1

这个功能只在JMockit版本1.17中引入,仅用于在NonStrictExpectation(){}块中使用对象引用,已弃用@Mocked的"value"属性值,该属性值用于"静态"部分模拟。现有用法应替换为"动态"部分模拟,通过将要部分模拟的实例或类传递给Expectations(Object...)构造函数,或应用MockUp类来实现。

请参考下面的链接:JMockit版本历史


我不使用@Mocked的value属性。 - trunkc

0

是的,这是部分模拟。通过在nonStrictExpectation(){}中进行小修正,您上面提到的代码也可以得到修复:

@Mocked
private System system;

new NonStrictExpectations() {{
    System.currentTimeMillis(); 
    result = 1438357206679L;
}};

long currentTime = System.currentTimeMillis();
assertEquals(1438357206679L, currentTime);

这个也应该可以工作。


1
此示例未使用部分模拟。 - John Mercier

0
我使用PowerMock来实现这个功能:
private void configureSystemClockMock(final int clockJump) {
    AtomicInteger callCount = new AtomicInteger();

    Answer<Integer> answer = invocation -> {
        callCount.addAndGet(1);
        return callCount.get() * clockJump;
    };

    PowerMockito.mockStatic(System.class);
    PowerMockito.doAnswer(answer).when(System.class);
    System.currentTimeMillis();
}

当然,您可以在调用中放置任何您想要的内容。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接