在Java 8的java.time API中嘲笑时间

乔达时间有一个很好的DateTimeUtils.setCurrentMillisFixed()来模拟时间。 这在testing中非常实用。 在Java 8的java.time API中是否有相同的东西 ?

最接近的是Clock对象。 您可以使用任何时候(或从系统当前时间)创build一个Clock对象。 所有的date.time对象now都重载了一个时钟对象,而不是当前的时间对象。 所以你可以使用dependency injection来注入一个特定的时钟:

 public class MyBean { private Clock clock; // dependency inject ... public void process(LocalDate eventDate) { if (eventDate.isBefore(LocalDate.now(clock)) { ... } } } 

有关更多详细信息,请参阅Clock JavaDoc

我使用了一个新类来隐藏Clock.fixed创build并简化testing:

 public class TimeMachine { private static Clock clock = Clock.systemDefaultZone(); private static ZoneId zoneId = ZoneId.systemDefault(); public static LocalDateTime now() { return LocalDateTime.now(getClock()); } public static void useFixedClockAt(LocalDateTime date){ clock = Clock.fixed(date.atZone(zoneId).toInstant(), zoneId); } public static void useSystemDefaultZoneClock(){ clock = Clock.systemDefaultZone(); } private static Clock getClock() { return clock ; } } 
 public class MyClass { public void doSomethingWithTime() { LocalDateTime now = TimeMachine.now(); ... } } 
 @Test public void test() { LocalDateTime twoWeeksAgo = LocalDateTime.now().minusWeeks(2); MyClass myClass = new MyClass(); TimeMachine.useFixedClockAt(twoWeeksAgo); myClass.doSomethingWithTime(); BankTime.useSystemDefaultZoneClock(); myClass.doSomethingWithTime(); ... } 

我发现使用Clock凌乱你的生产代码。

您可以使用JMockit或PowerMock来模拟testing代码中的静态方法调用。 JMockit示例:

 @Test public void testSth() { LocalDate today = LocalDate.of(2000, 6, 1); new Expectations(LocalDate.class) {{ LocalDate.now(); result = today; }}; Assert.assertEquals(LocalDate.now(), today); } 

编辑 :在阅读Jon Skeet 在这里对类似问题的回答的评论之后,我不同意我的过去的自我。 这个说法比任何其他的说服都让我相信,当你嘲笑静态方法时,你不能平行testing。

不过,如果你不得不处理遗留代码,你仍然可以使用静态模拟。

我用了一个字段

 private Clock clock; 

接着

 LocalDate.now(clock); 

在我的生产代码。 然后我在我的unit testing中使用Mockito来使用Clock.fixed()来模拟时钟:

 @Mock private Clock clock; private Clock fixedClock; 

嘲讽:

 fixedClock = Clock.fixed(Instant.now(), ZoneId.systemDefault()); doReturn(fixedClock.instant()).when(clock).instant(); doReturn(fixedClock.getZone()).when(clock).getZone(); 

断言:

 assertThat(expectedLocalDateTime, is(LocalDate.now(fixedClock))); 

在EasyMock的Java 8 Web应用程序中,这里有一种工作方式可以将当前系统时间覆盖到特定date以进行JUnittesting

乔达时间确实很好(谢谢斯蒂芬,布莱恩,你让我们的世界变得更美好),但是我没有被允许使用它。

经过一番试验后,我终于想出了一个方法,用EasyMock在Java 8的java.time API中调用特定date

  • 没有Joda时间API
  • 没有PowerMock。

以下是需要做的事情:

在被testing的类中需要做什么

步骤1

添加一个新的java.time.Clock属性到被testing的类MyService ,并确保新的属性将被初始化为具有实例化块或构造函数的默认值:

 import java.time.Clock; import java.time.LocalDateTime; public class MyService { // (...) private Clock clock; public Clock getClock() { return clock; } public void setClock(Clock newClock) { clock = newClock; } public void initDefaultClock() { setClock( Clock.system( Clock.systemDefaultZone().getZone() // You can just as well use // java.util.TimeZone.getDefault().toZoneId() instead ) ); } { initDefaultClock(); } // initialisation in an instantiation block, but // it can be done in a constructor just as well // (...) } 

第2步

将新的属性clock注入到调用当前date时间的方法中。 例如,在我的情况下,我不得不执行检查数据库中存储的date是否发生在LocalDateTime.now()之前,我使用LocalDateTime.now(clock)重新放置,如下所示:

 import java.time.Clock; import java.time.LocalDateTime; public class MyService { // (...) protected void doExecute() { LocalDateTime dateToBeCompared = someLogic.whichReturns().aDate().fromDB(); while (dateToBeCompared.isBefore(LocalDateTime.now(clock))) { someOtherLogic(); } } // (...) } 

testing课上需要做什么?

第3步

在testing类中,创build一个模拟时钟对象,然后在调用testing方法doExecute()之前将其注入到被testing类的实例中,然后将其重置,如下所示:

 import java.time.Clock; import java.time.LocalDateTime; import java.time.OffsetDateTime; import org.junit.Test; public class MyServiceTest { // (...) private int year = 2017; // Be this a specific private int month = 2; // date we need private int day = 3; // to simulate. @Test public void doExecuteTest() throws Exception { // (...) EasyMock stuff like mock(..), expect(..), replay(..) and whatnot MyService myService = new MyService(); Clock mockClock = Clock.fixed( LocalDateTime.of(year, month, day, 0, 0).toInstant(OffsetDateTime.now().getOffset()), Clock.systemDefaultZone().getZone() // or java.util.TimeZone.getDefault().toZoneId() ); myService.setClock(mockClock); // set it before calling the tested method myService.doExecute(); // calling tested method myService.initDefaultClock(); // reset the clock to default right afterwards with our own previously created method // (...) remaining EasyMock stuff: verify(..) and assertEquals(..) } } 

在debugging模式下检查它,你会看到2017年2月3日的date已经被正确地注入myService实例并用于比较指令,然后通过initDefaultClock()正确地重置为当前date。

这个例子甚至展示了如何将Instant和LocalTime结合起来( 对转换问题的详细解释 )

正在testing的课程

 import java.time.Clock; import java.time.LocalTime; public class TimeMachine { private LocalTime from = LocalTime.MIDNIGHT; private LocalTime until = LocalTime.of(6, 0); private Clock clock = Clock.systemDefaultZone(); public boolean isInInterval() { LocalTime now = LocalTime.now(clock); return now.isAfter(from) && now.isBefore(until); } } 

Groovytesting

 import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.Parameterized import java.time.Clock import java.time.Instant import static java.time.ZoneOffset.UTC import static org.junit.runners.Parameterized.Parameters @RunWith(Parameterized) class TimeMachineTest { @Parameters(name = "{0} - {2}") static data() { [ ["01:22:00", true, "in interval"], ["23:59:59", false, "before"], ["06:01:00", false, "after"], ]*.toArray() } String time boolean expected TimeMachineTest(String time, boolean expected, String testName) { this.time = time this.expected = expected } @Test void test() { TimeMachine timeMachine = new TimeMachine() timeMachine.clock = Clock.fixed(Instant.parse("2010-01-01T${time}Z"), UTC) def result = timeMachine.isInInterval() assert result == expected } }