1. Overview
1.概述
In this quick tutorial, we’ll focus on different ways to override the system time for testing.
在这个快速教程中,我们将重点介绍不同的方法来覆盖系统时间进行测试。
Sometimes there’s a logic around the current date in our code. Maybe some function calls such as new Date() or Calendar.getInstance(), which eventually are going to call System.CurrentTimeMillis.
有时在我们的代码中会有一个围绕当前日期的逻辑。也许是一些函数调用,如new Date()或Calendar.getInstance(),最终会调用System.CurrentTimeMillis。
For an introduction to the use of Java Clock, please refer to this article here. Or, to the use of AspectJ, here.
关于Java Clock的使用介绍,请参考这里的文章。或者,关于AspectJ的使用,这里。
2. Using Clock in java.time
2.在 java.time中使用时钟
The java.time package in Java 8 includes an abstract class java.time.Clock with the purpose of allowing alternate clocks to be plugged in as and when required. With that, we can plug our own implementation or find one that is already made to satisfy our needs.
java.time包在Java 8中包括一个抽象类java.time.Clock,目的是允许在需要时插入备用的时钟。有了这个类,我们可以插入我们自己的实现,或者找到一个已经制作好的实现来满足我们的需求。
To accomplish our goals, the above library includes static methods to yield special implementations. We’re going to use two of them which returns an immutable, thread-safe and serializable implementation.
为了实现我们的目标,上述库包括静态方法来产生特殊的实现。我们将使用其中的两个方法来返回一个不可变的、线程安全的和可序列化的实现。
The first one is fixed. From it, we can obtain a Clock that always returns the same Instant, ensuring that the tests aren’t dependent on the current clock.
第一个是fixed。从它,我们可以得到一个Clock,它总是返回相同的Instant,确保测试不依赖于当前的时钟。
To use it, we need an Instant and a ZoneOffset:
要使用它,我们需要一个Instant和一个ZoneOffset。
Instant.now(Clock.fixed(
Instant.parse("2018-08-22T10:00:00Z"),
ZoneOffset.UTC))
The second static method is offset. In this one, a clock wraps another clock that makes it the returned object capable of getting instants that are later or earlier by the specified duration.
第二个静态方法是offset。在这个方法中,一个时钟包裹着另一个时钟,使其成为返回的对象,能够获得晚于或早于指定时间的实例。
In other words, it’s possible to simulate running in the future, in the past, or in any arbitrary point in time:
换句话说,可以模拟在未来、过去或任何任意时间点上的运行。
Clock constantClock = Clock.fixed(ofEpochMilli(0), ZoneId.systemDefault());
// go to the future:
Clock clock = Clock.offset(constantClock, Duration.ofSeconds(10));
// rewind back with a negative value:
clock = Clock.offset(constantClock, Duration.ofSeconds(-5));
// the 0 duration returns to the same clock:
clock = Clock.offset(constClock, Duration.ZERO);
With the Duration class, it’s possible to manipulate from nanoseconds to days. Also, we can negate a duration, which means to get a copy of this duration with the length negated.
通过Duration类,我们可以操作从纳秒到天。此外,我们还可以否定一个持续时间,这意味着可以得到这个持续时间的一个副本,其长度被否定。
3. Using Aspect-Oriented Programming
3.使用面向方面的编程
Another way to override the system time is by AOP. With this approach, we’re able to weave the System class to return a predefined value which we can set within our test cases.
另一种覆盖系统时间的方法是通过AOP。通过这种方法,我们能够编织System类来返回一个预定义的值,我们可以在我们的测试案例中设置这个值。
Also, it’s possible to weave the application classes to redirect the call to System.currentTimeMillis() or to new Date() to another utility class of our own.
此外,还可以通过编织应用类,将对System.currentTimeMillis()或new Date()的调用重定向到我们自己的另一个实用类。
One way to implement this is through the use of AspectJ:
实现这一目标的方法之一是通过使用AspectJ。
public aspect ChangeCallsToCurrentTimeInMillisMethod {
long around():
call(public static native long java.lang.System.currentTimeMillis())
&& within(user.code.base.pckg.*) {
return 0;
}
}
In the above example, we’re catching every call to System.currentTimeMillis() inside a specified package, which in this case is user.code.base.pckg.*, and returning zero every time that this event happens.
在上面的例子中,我们正在捕捉指定包内对System.currentTimeMillis()的每一次调用,在这种情况下是user.code.base.pckg.*,并且在每次发生这种事件时返回0。
It’s in this place where we can declare our own implementation to obtain the desired time in milliseconds.
正是在这个地方,我们可以声明我们自己的实现,以获得所需的时间,单位是毫秒。
One advantage of using AspectJ is that it operates on bytecode level directly, so it doesn’t need the original source code to work.
使用AspectJ的一个好处是,它直接在字节码水平上操作,所以它不需要原始的源代码就可以工作。
For that reason, we wouldn’t need to recompile it.
出于这个原因,我们不需要重新编译它。
4. Mocking the Instant.now() Method
4.模拟Instant.now()方法
We can use the Instant class to represent an instantaneous point on the timeline. Normally, we can use it to record event time-stamps in our application. The now() method of this class allows us to get the current instant from the system clock in the UTC timezone.
我们可以使用Instant类来表示时间线上的一个瞬时点。通常,我们可以用它来记录我们应用程序中的事件时间戳。这个类的now()方法允许我们从UTC时区的系统时钟中获取当前的瞬间。
Let’s see some alternatives for changing its behavior when we test.
让我们看看在测试时改变其行为的一些替代方案。
4.1. Overloading now() With a Clock
4.1.用一个Clock重载now()
We can overload the now() method with a fixed Clock instance. Many of the classes in the java.time package have a now() method that takes a Clock parameter, which makes this our preferred approach:
我们可以用一个固定的Clock实例来重载now()/em>方法。java.time包中的许多类都有一个now()方法,它需要一个Clock参数,这使我们更倾向于采用这种方法。
@Test
public void givenFixedClock_whenNow_thenGetFixedInstant() {
String instantExpected = "2014-12-22T10:15:30Z";
Clock clock = Clock.fixed(Instant.parse(instantExpected), ZoneId.of("UTC"));
Instant instant = Instant.now(clock);
assertThat(instant.toString()).isEqualTo(instantExpected);
}
4.2. Using Mockito
4.2.使用Mockito
In addition, if we need to modify the behavior of the now() method without sending parameters, we can use Mockito:
此外,如果我们需要修改now()方法的行为而不发送参数,我们可以使用Mockito。
@Test
public void givenInstantMock_whenNow_thenGetFixedInstant() {
String instantExpected = "2014-12-22T10:15:30Z";
Clock clock = Clock.fixed(Instant.parse(instantExpected), ZoneId.of("UTC"));
Instant instant = Instant.now(clock);
try (MockedStatic<Instant> mockedStatic = mockStatic(Instant.class)) {
mockedStatic.when(Instant::now).thenReturn(instant);
Instant now = Instant.now();
assertThat(now.toString()).isEqualTo(instantExpected);
}
}
4.3. Using JMockit
4.3.使用JMockit
Alternatively, we can use the JMockit library.
另外,我们可以使用JMockit库。
JMockit offers us two ways of mocking a static method. One is using the MockUp class:
JMockit为我们提供了两种模拟静态方法的方式。一种是使用MockUp类。
@Test
public void givenInstantWithJMock_whenNow_thenGetFixedInstant() {
String instantExpected = "2014-12-21T10:15:30Z";
Clock clock = Clock.fixed(Instant.parse(instantExpected), ZoneId.of("UTC"));
new MockUp<Instant>() {
@Mock
public Instant now() {
return Instant.now(clock);
}
};
Instant now = Instant.now();
assertThat(now.toString()).isEqualTo(instantExpected);
}
And the another is using the Expectations class:
而另一个是使用Expectations类。
@Test
public void givenInstantWithExpectations_whenNow_thenGetFixedInstant() {
Clock clock = Clock.fixed(Instant.parse("2014-12-23T10:15:30.00Z"), ZoneId.of("UTC"));
Instant instantExpected = Instant.now(clock);
new Expectations(Instant.class) {
{
Instant.now();
result = instantExpected;
}
};
Instant now = Instant.now();
assertThat(now).isEqualTo(instantExpected);
}
5. Mocking the LocalDateTime.now() Method
5.嘲弄LocalDateTime.now()方法
Another useful class in the java.time package is the LocalDateTime class. This class represents a date-time without a timezone in the ISO-8601 calendar system. The now() method of this class allows us to get the current date-time from the system clock in the default timezone.
java.time包中另一个有用的类是LocalDateTime类。这个类代表了ISO-8601日历系统中没有时区的日期时间。这个类的now()方法允许我们从默认时区的系统时钟中获取当前日期时间。
We can use the same alternatives to mock it as we saw before. For example, overloading now() with a fixed Clock:
我们可以像之前看到的那样使用同样的替代方法来模拟它。例如,用一个固定的Clock重载now()。
@Test
public void givenFixedClock_whenNow_thenGetFixedLocalDateTime() {
Clock clock = Clock.fixed(Instant.parse("2014-12-22T10:15:30.00Z"), ZoneId.of("UTC"));
String dateTimeExpected = "2014-12-22T10:15:30";
LocalDateTime dateTime = LocalDateTime.now(clock);
assertThat(dateTime).isEqualTo(dateTimeExpected);
}
6. Conclusion
6.结论
In this article, we’ve explored different ways to override the system time for testing. First, we looked at the native package java.time and its Clock class. Next, we saw how to apply an aspect to weave the System class. Finally, we saw different alternatives to mocking the now() method on Instant and LocalDateTime classes.
在这篇文章中,我们探讨了为测试而覆盖系统时间的不同方法。首先,我们看了本地包java.time和它的Clock类。接下来,我们看到如何应用一个方面来编织System类。最后,我们看到了在Instant和LocalDateTime类上嘲弄now()方法的不同选择。