Java 8 的 java.time API 中的模拟时间

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/24491260/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-14 12:34:47  来源:igfitidea点击:

Mocking time in Java 8's java.time API

javadatetimemockingjava-8java-time

提问by neu242

Joda Time has a nice DateTimeUtils.setCurrentMillisFixed()to mock time.

Joda Time 有一个很好的DateTimeUtils.setCurrentMillisFixed()来模拟时间。

It's very practical in tests.

在测试中非常实用。

Is there an equivalent in Java 8's java.time API?

Java 8 的 java.time API 中是否有等价物?

采纳答案by dkatzel

The closest thing is the Clockobject. You can create a Clock object using any time you want (or from the System current time). All date.time objects have overloaded nowmethods that take a clock object instead for the current time. So you can use dependency injection to inject a Clock with a specific time:

最接近的东西是Clock物体。您可以使用任何时间(或从系统当前时间)创建时钟对象。所有 date.time 对象都有重载的now方法,这些方法采用时钟对象代替当前时间。所以你可以使用依赖注入来注入具有特定时间的时钟:

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

See Clock JavaDocfor more details

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

回答by LuizSignorelli

I used a new class to hide the Clock.fixedcreation and simplify the tests:

我使用了一个新类来隐藏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();

    ...
}

回答by Stefan Haberl

I find using Clockclutters your production code.

我发现使用Clock混乱您的生产代码。

You can use JMockitor PowerMockto mock static method invocations in your test code. Example with JMockit:

您可以使用JMockitPowerMock来模拟测试代码中的静态方法调用。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);
}

EDIT: After reading the comments on Jon Skeet's answer to a similar question here on SOI disagree with my past self. More than anything else the argument convinced me that you cannot parallize tests when you mock static methods.

编辑:在阅读了关于 Jon Skeet 对类似问题的回答的评论后,我不同意我过去的自我。最重要的是,这个论点让我相信,当你模拟静态方法时,你不能并行化测试。

You can/must still use static mocking if you have to deal with legacy code, though.

但是,如果您必须处理遗留代码,您仍然可以/必须使用静态模拟。

回答by banterCZ

This example even shows how to combine Instant and LocalTime (detailed explanation of issues with the conversion)

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

A class under test

被测类

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);
    }

}

A Groovy test

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
    }

}

回答by Claas Wilke

I used a field

我用了一个字段

private Clock clock;

and then

进而

LocalDate.now(clock);

in my production code. Then I used Mockito in my unit tests to mock the Clock using Clock.fixed():

在我的生产代码中。然后我在单元测试中使用 Mockito 使用 Clock.fixed() 模拟时钟:

@Mock
private Clock clock;
private Clock fixedClock;

Mocking:

嘲讽:

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

Assertion:

断言:

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

回答by KiriSakow

Here's a working way to override current system time to a specific date for JUnit testing purposes in a Java 8 web application with EasyMock

这是使用 EasyMock 在 Java 8 Web 应用程序中将当前系统时间覆盖到特定日期以进行 JUnit 测试的工作方法

Joda Time is sure nice (thank you Stephen, Brian, you've made our world a better place) but I wasn't allowed to use it.

Joda Time 确实不错(谢谢 Stephen、Brian,你们让我们的世界变得更美好)但我不被允许使用它。

After some experimenting, I eventually came up with a way to mock time to a specific date in Java 8's java.time API with EasyMock

经过一些试验,我最终想出了一种使用 EasyMock 在 Java 8 的 java.time API 中模拟时间到特定日期的方法

  • without Joda Time API
  • and without PowerMock.
  • 没有 Joda 时间 API
  • 并且没有 PowerMock。

Here's what needs to be done:

以下是需要做的事情:

What needs to be done in the tested class

测试类需要做什么

Step 1

第1步

Add a new java.time.Clockattribute to the tested class MyServiceand make sure the new attribute will be initialized properly at default values with an instantiation block or a constructor:

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
  // (...)
}

Step 2

第2步

Inject the new attribute clockinto the method which calls for a current date-time. For instance, in my case I had to perform a check of whether a date stored in dataase happened before LocalDateTime.now(), which I remplaced with LocalDateTime.now(clock), like so:

将新属性clock注入到调用当前日期时间的方法中。例如,在我的情况下,我必须检查存储在 dataase 中的日期是否发生在 之前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();
    }
  }
  // (...) 
}

What needs to be done in the test class

测试类需要做什么

Step 3

第 3 步

In the test class, create a mock clock object and inject it into the tested class's instance just before you call the tested method doExecute(), then reset it back right afterwards, like so:

在测试类中,创建一个模拟时钟对象,并在调用测试方法之前将其注入到测试类的实例中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(..)
    }
  }

Check it in debug mode and you will see the date of 2017 Feb 3 has been correctly injected into myServiceinstance and used in the comparison instruction, and then has been properly reset to current date with initDefaultClock().

在调试模式下检查,您将看到 2017 年 2 月 3 日的日期已正确注入myService实例并在比较指令中使用,然后已正确重置为当前日期initDefaultClock()

回答by nazar_art

I need LocalDateinstance instead of LocalDateTime.
With such reason I created following utility class:

我需要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);
    }
}

And usage for it is like:

它的用法是:

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());
    }
}

Output:

输出:

2019-01-03
1998-12-12
2019-01-03

Replaced all creation LocalDate.now()to Clock.getCurrentDate()in project.

将所有创建替换LocalDate.now()Clock.getCurrentDate()项目中。

Because it is spring bootapplication. Before testprofile execution just set a predefined date for all tests:

因为它是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);
        }
    }
}

And add to spring.factories:

并添加到spring.factories

org.springframework.context.ApplicationListener=com.init.TestProfileConfigurer

org.springframework.context.ApplicationListener=com.init.TestProfileConfigurer

回答by pasquale

With the help of PowerMockito for a spring boot test you can mock the ZonedDateTime. You need the following.

借助 PowerMockito 进行春季启动测试,您可以模拟ZonedDateTime. 您需要以下内容。

Annotations

注释

On the test class you need to prepare the servicewhich uses the the ZonedDateTime.

在测试类中,您需要准备使用ZonedDateTime.

@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(SpringRunner.class)
@PrepareForTest({EscalationService.class})
@SpringBootTest
public class TestEscalationCases {
  @Autowired
  private EscalationService escalationService;
  //...
}

Test case

测试用例

In the test you can prepare a desired time, and get it in response of the method call.

在测试中,您可以准备所需的时间,并在方法调用的响应中获取它。

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