Подтвердить что ты не робот

Время от времени в Java 8 java.time API

У Joda Time есть хороший DateTimeUtils.setCurrentMillisFixed(), чтобы имитировать время.

Это очень практично в тестах.

Есть ли эквивалент в Java 8 API java.time?

4b9b3361

Ответ 1

Ближайшей задачей является объект Clock. Вы можете создать объект Clock в любое время (или из текущего времени системы). Все объекты date.time имеют перегруженные методы now, которые вместо этого используют объект clock вместо текущего времени. Таким образом, вы можете использовать инъекцию зависимостей для инъекции часов с определенным временем:

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

Подробнее см. Clock JavaDoc

Ответ 2

Я использовал новый класс, чтобы скрыть создание 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();

    ...
}

Ответ 3

Я нахожу, используя 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 ответ на аналогичный вопрос здесь, на SO, я не согласен с моим прошлым я. Больше всего аргумент убедил меня в том, что вы не можете парализовать тесты, когда высмеиваете статические методы.

Вы можете/должны по-прежнему использовать статическое моделирование, если вам приходится иметь дело с устаревшим кодом.

Ответ 4

Я использовал поле

private Clock clock;

а затем

LocalDate.now(clock);

в моем производственном коде. Затем я использовал Mockito в своих модульных тестах, чтобы издеваться над Clock, используя 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();

Утверждение:

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

Ответ 5

Здесь рабочий способ переопределить текущее системное время на конкретную дату для целей тестирования JUnit в веб-приложении Java 8 с помощью EasyMock

Joda Time уверен, хорошо (спасибо Стивен, Брайан, вы сделали наш мир лучше), но мне не разрешили использовать его.

После некоторых экспериментов я в конечном итоге придумал способ высмеять время до определенной даты в Java 8 java.time API с помощью EasyMock

  • без API Joda Time
  • и без PowerMock.

Вот что нужно сделать:

Что нужно сделать в тестируемом классе

Шаг 1

Добавьте новый атрибут 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
  // (...)
}

Шаг 2

Ввести новый атрибут 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();
    }
  }
  // (...) 
}

Что нужно сделать в тестовом классе

Шаг 3

В тестовом классе создайте объект mock clock и введите его в тестируемый экземпляр класса непосредственно перед тем, как вы вызовете проверенный метод doExecute(), затем reset он снова появится прямо так:

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 3 февраля была правильно введена в экземпляр MyService и использована в инструкции сравнения, а затем была правильно reset до текущей даты с помощью initDefaultClock().

Ответ 6

В этом примере показано, как объединить Instant и LocalTime (подробное объяснение проблем с конверсией)

Класс под тестом

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

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

Мне нужен экземпляр 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() в проекте.

Потому что это приложение с весенней загрузкой. Перед test профиля 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

С помощью 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 
}