在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 } }