В Joda Time есть приятный DateTimeUtils.setCurrentMillisFixed () для имитации времени.
Это очень практично в тестах.
Есть ли эквивалент в Java 8 java.time API ?
В Joda Time есть приятный DateTimeUtils.setCurrentMillisFixed () для имитации времени.
Это очень практично в тестах.
Есть ли эквивалент в Java 8 java.time API ?
Ответы:
Самое близкое - это 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();
...
}
getClock()
метод и напрямую использовать поле. Этот метод не добавляет ничего, кроме нескольких строк кода.
Я использовал поле
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);
}
РЕДАКТИРОВАТЬ : после прочтения комментариев к ответу Джона Скита на аналогичный вопрос здесь, ТАК, я не согласен со своим прошлым. Больше всего на свете этот аргумент убедил меня в том, что нельзя парализовать тесты, когда вы имитируете статические методы.
Тем не менее, вы можете / должны использовать статические имитации, если вам приходится иметь дело с устаревшим кодом.
Мне нужен 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
Joda Time действительно хорош (спасибо, Стивен, Брайан, вы сделали наш мир лучше), но мне не разрешили его использовать.
После некоторых экспериментов я в конце концов придумал способ имитировать время до определенной даты в Java 8 java.time API с EasyMock.
Вот что нужно сделать:
Добавьте новый 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(..)
}
}
Проверьте его в режиме отладки, и вы увидите, что дата 3 февраля 2017 года была правильно введена в myService
экземпляр и использована в инструкции сравнения, а затем была правильно сброшена на текущую дату с помощью initDefaultClock()
.
В этом примере даже показано, как объединить 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
}
}
С помощью 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
}
Clock.fixed
полезно при тестировании, в то времяClock.system
илиClock.systemUTC
могут быть использованы в приложении.