it-swarm.com.ru

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

У Joda Time есть Nice DateTimeUtils.setCurrentMillisFixed () для имитации времени. 

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

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

70
neu242

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

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

Смотрите Часы JavaDoc для более подробной информации

57
dkatzel

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

    ...
}
19
LuizSignorelli

Я считаю, что использование 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);
}

EDIT: Прочитав комментарии к ответу Джона Скита на похожий вопрос здесь SO я не согласен с моим прошлым я. Больше всего аргумент убедил меня в том, что вы не можете парализовать тесты, когда вы высмеиваете статические методы.

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

7
Stefan Haberl

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

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)));
5
Claas Wilke

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

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

После некоторых экспериментов я, в конце концов, нашел способ смоделировать время до определенной даты в Java.time 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().

1
KiriSakow

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

}
0
banterCZ

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

Мне нужен экземпляр 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 bootapplication. Перед выполнением профиля 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

0
nazar_art