Joda Time对模拟时间有一个很好的DateTimeUtils.setCurrentMillisFixed()。
这在测试中非常实用。
Java 8's java.time API中是否有等价物?
答案 0 :(得分:58)
最接近的是Clock
对象。您可以随时使用(或从系统当前时间)创建Clock对象。所有date.time对象都有重载now
方法,这些方法将时钟对象替换为当前时间。因此,您可以使用依赖注入来注入具有特定时间的时钟:
public class MyBean {
private Clock clock; // dependency inject
...
public void process(LocalDate eventDate) {
if (eventDate.isBefore(LocalDate.now(clock)) {
...
}
}
}
有关详细信息,请参阅Clock JavaDoc
答案 1 :(得分:19)
我使用了一个新类来隐藏Clock.fixed
创建并简化测试:
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();
TimeMachine.useSystemDefaultZoneClock();
myClass.doSomethingWithTime();
...
}
答案 2 :(得分:6)
我发现使用 Clock
会使您的生产代码混乱。
您可以使用JMockit或PowerMock在测试代码中模拟静态方法调用。 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对类似question here on SO的回答的评论后,我不同意我过去的自我。最重要的是,这个论点使我确信在模拟静态方法时你不能平行化测试。
但是,如果必须处理遗留代码,则可以/必须仍然使用静态模拟。
答案 3 :(得分:5)
我使用了一个字段
private Clock clock;
然后
LocalDate.now(clock);
在我的生产代码中。然后我在单元测试中使用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)));
答案 4 :(得分:2)
有点晚了,但这里是我使用 Kotlin 中的 java.date
API 来模拟时间的方法:
val now = LocalDate.of(2021, Month.FEBRUARY, 19)
val clock = Clock.fixed(Instant.ofEpochSecond(
now.atStartOfDay().toEpochSecond(ZoneOffset.UTC)
), ZoneId.systemDefault())
然后你可以把你的时钟传给班级来测试
val classToTest = MyClass(clock)
当然,在可测试的类中,您将使用时钟来检索日期或时间:
class MyClass(private val clock: Clock = Clock.systemDefaultZone()) {
// ...
fun doSomething() = LocalDate.now(clock)...
答案 5 :(得分:1)
经过一些实验,我最终想出了一种方法,可以使用EasyMock在Java 8的java.time API中模拟特定日期
以下是需要做的事情:
向测试类java.time.Clock
添加新的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
// (...)
}
将新属性clock
注入到调用当前日期时间的方法中。例如,在我的情况下,我必须检查存储在数据库中的日期是否发生在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();
}
}
// (...)
}
在测试类中,创建一个模拟时钟对象,并在调用测试方法doExecute()
之前将其注入到测试类的实例中,然后将其重新调回,如下所示:
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(..)
}
}
在调试模式下检查它,您将看到2017年2月3日的日期已正确注入myService
实例并在比较说明中使用,然后已使用{{1}正确重置为当前日期}。
答案 6 :(得分:0)
此示例甚至显示了如何组合Instant和LocalTime(detailed explanation of issues with the conversion)
受测试的课程
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);
}
}
Groovy测试
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
}
}
答案 7 :(得分:0)
我需要LocalDate
实例而不是LocalDateTime
。
因此,我创建了以下实用程序类:
public final class Clock {
private static long time;
private Clock() {
}
public static void setCurrentDate(LocalDate date) {
Clock.time = date.toEpochDay();
}
public static LocalDate getCurrentDate() {
return LocalDate.ofEpochDay(getDateMillis());
}
public static void resetDate() {
Clock.time = 0;
}
private static long getDateMillis() {
return (time == 0 ? LocalDate.now().toEpochDay() : time);
}
}
其用法如下:
class ClockDemo {
public static void main(String[] args) {
System.out.println(Clock.getCurrentDate());
Clock.setCurrentDate(LocalDate.of(1998, 12, 12));
System.out.println(Clock.getCurrentDate());
Clock.resetDate();
System.out.println(Clock.getCurrentDate());
}
}
输出:
2019-01-03
1998-12-12
2019-01-03
将项目中的所有创建LocalDate.now()
替换为Clock.getCurrentDate()
。
因为它是 spring boot 应用程序。在test
配置文件执行之前,只需为所有测试设置一个预定义日期:
public class TestProfileConfigurer implements ApplicationListener<ApplicationPreparedEvent> {
private static final LocalDate TEST_DATE_MOCK = LocalDate.of(...);
@Override
public void onApplicationEvent(ApplicationPreparedEvent event) {
ConfigurableEnvironment environment = event.getApplicationContext().getEnvironment();
if (environment.acceptsProfiles(Profiles.of("test"))) {
Clock.setCurrentDate(TEST_DATE_MOCK);
}
}
}
并添加到spring.factories
:
org.springframework.context.ApplicationListener = com.init.TestProfileConfigurer
答案 8 :(得分:0)
借助PowerMockito进行的春季启动测试,您可以模拟ZonedDateTime
。
您需要以下内容。
在测试类上,您需要准备使用ZonedDateTime
的服务。
@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(SpringRunner.class)
@PrepareForTest({EscalationService.class})
@SpringBootTest
public class TestEscalationCases {
@Autowired
private EscalationService escalationService;
//...
}
在测试中,您可以准备所需的时间,并根据方法调用获得时间。
@Test
public void escalateOnMondayAt14() throws Exception {
ZonedDateTime preparedTime = ZonedDateTime.now();
preparedTime = preparedTime.with(DayOfWeek.MONDAY);
preparedTime = preparedTime.withHour(14);
PowerMockito.mockStatic(ZonedDateTime.class);
PowerMockito.when(ZonedDateTime.now(ArgumentMatchers.any(ZoneId.class))).thenReturn(preparedTime);
// ... Assertions
}
答案 9 :(得分:0)
使用 jmockit:
代码:
// Mocking time as 9am
final String mockTime = "09:00:00"
new MockUp<LocalTime>() {
@Mock
public LocalTime now() {
return LocalTime.parse(mockTime);
}
};
进口:
import mockit.MockUp;
import mockit.Mock;
依赖:
<groupId>org.jmockit</groupId>
<artifactId>jmockit</artifactId>
<version>1.41</version>