Время издевательства в Java 8 API Java.

У Joda Time есть хороший DateTimeUtils.setCurrentMillisFixed(), чтобы имитировать время. Это очень практично в тестах. Есть ли эквивалент в Java 8 API java.time?

12 ответов

Решение

Самая близкая вещь 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 для более подробной информации.

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

    ...
}

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

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

Я использую 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);
}

РЕДАКТИРОВАТЬ: После прочтения комментариев на ответ Джона Скита на аналогичный вопрос здесь, на SO, я не согласен с моим прошлым я. Больше всего аргумент убедил меня в том, что вы не можете парализовать тесты, когда высмеиваете статические методы.

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

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

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

После некоторых экспериментов я в конце концов нашел способ смоделировать время к определенной дате в Java 8 API Java 8 с EasyMock

  • без Joda Time API
  • и без 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 в метод, который вызывает для текущей даты-времени. Например, в моем случае я должен был проверить, произошла ли дата, сохраненная в базе данных, раньше 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

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

Немного поздно, но вот что я использую, чтобы поиздеваться над временем, используя 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)...

Я нуждаюсь 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 В профиле выполнения просто установите предварительно определенную дату для всех тестов:

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

я использовал java.time.Clock с зависимостью mockito

      testImplementation("org.mockito:mockito-core")
testImplementation("org.mockito:mockito-inline")

Класс обслуживания использует поле, которое будет имитироваться в тесте.

      @Service
public class DeliveryWithDateService {
    private final Clock clock = Clock.systemUTC();

    public Delivery plan(UUID orderId) {
        return Delivery.builder()
                .id(UUID.randomUUID())
                .orderId(orderId)
                .createdAt(ZonedDateTime.now(clock))
                .plannedAt(ZonedDateTime.now(clock)
                        .plusDays(1)
                        .withHour(8)
                        .truncatedTo(ChronoUnit.HOURS))
                .build();
    }

    public Delivery ship(Delivery delivery) {
        return Delivery.builder()
                .id(delivery.getId())
                .orderId(delivery.getOrderId())
                .createdAt(delivery.getCreatedAt())
                .shippedAt(ZonedDateTime.now(clock))
                .build();
    }
}

@Value
@Builder
public class Delivery {
    private UUID id;
    private UUID orderId;
    private ZonedDateTime createdAt;
    private ZonedDateTime plannedAt;
    private ZonedDateTime shippedAt;
}

Модульный тест использует Mockito.mockStatic издеваться над Clock.

      @SpringBootTest
public class DeliveryWithDateServiceTest {
    @Autowired
    private DeliveryWithDateService deliveryService;

    private static Clock clock;
    private static ZonedDateTime now;

    @BeforeAll
    static void setupClock() {
        clock = Clock.fixed(
                Instant.parse("2020-12-01T10:05:23.653Z"),
                ZoneId.of("Europe/Prague"));
        now = ZonedDateTime.now(clock);

        var clockMock = Mockito.mockStatic(Clock.class);
        clockMock.when(Clock::systemUTC).thenReturn(clock);
    }

    @Test
    void delivery_is_planned() {
        var orderId = UUID.randomUUID();
        var delivery = deliveryService.plan(orderId);

        var tomorrowAt8am = now.plusDays(1).withHour(8).truncatedTo(ChronoUnit.HOURS);

        assertAll(
                () -> assertThat(delivery).isNotNull(),
                () -> assertThat(delivery.getId()).isNotNull(),
                () -> assertThat(delivery.getOrderId()).isEqualTo(orderId),
                () -> assertThat(delivery.getCreatedAt()).isEqualTo(now),
                () -> assertThat(delivery.getPlannedAt()).isEqualTo(tomorrowAt8am),
                () -> assertThat(delivery.getShippedAt()).isNull()
        );
    }

    @Test
    void delivery_is_shipped() {
        var delivery = deliveryService.plan(UUID.randomUUID());
        var shipped = deliveryService.ship(delivery);
        assertAll(
                () -> assertThat(shipped).isNotNull(),
                () -> assertThat(shipped.getId()).isEqualTo(delivery.getId()),
                () -> assertThat(shipped.getOrderId()).isEqualTo(delivery.getOrderId()),
                () -> assertThat(shipped.getCreatedAt()).isEqualTo(delivery.getCreatedAt()),
                () -> assertThat(shipped.getShippedAt()).isEqualTo(now)
        );
    }
}

В этом примере даже показано, как объединить 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);
    }

}

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
    }

}

вот ответ: https://gabstory.com/70?category=933660

      import com.nhaarman.mockitokotlin2.given
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.mockito.Mock
import org.mockito.Mockito.mockStatic
import org.mockito.junit.jupiter.MockitoExtension
import org.springframework.data.projection.ProjectionFactory
import org.springframework.data.projection.SpelAwareProxyProjectionFactory
import java.time.Clock
import java.time.ZonedDateTime

@ExtendWith(MockitoExtension::class)
class MyTest {
   private val clock = Clock.fixed(ZonedDateTime.parse("2021-10-25T00:00:00.000+09:00[Asia/Seoul]").toInstant(), SEOUL_ZONE_ID)
   
    @BeforeEach
    fun setup() {
        runCatching {
            val clockMock = mockStatic(Clock::class.java)
            clockMock.`when`<Clock>(Clock::systemDefaultZone).thenReturn(clock)
        }
    }
    
    @Test
    fun today(){
      assertEquals("2021-10-25T00:00+09:00[Asia/Seoul]", ZonedDateTime.now().toString())
    }
}

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

Используйте 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>
Другие вопросы по тегам