У Joda Time есть хороший DateTimeUtils.setCurrentMillisFixed(), чтобы имитировать время.
Это очень практично в тестах.
Есть ли эквивалент в Java 8 API java.time?
У Joda Time есть хороший DateTimeUtils.setCurrentMillisFixed(), чтобы имитировать время.
Это очень практично в тестах.
Есть ли эквивалент в Java 8 API java.time?
Ближайшей задачей является объект 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
Я использовал новый класс, чтобы скрыть создание 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();
...
}
Я нахожу, используя 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, я не согласен с моим прошлым я. Больше всего аргумент убедил меня в том, что вы не можете парализовать тесты, когда высмеиваете статические методы.
Вы можете/должны по-прежнему использовать статическое моделирование, если вам приходится иметь дело с устаревшим кодом.
Я использовал поле
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)));
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 в метод, который вызывает текущую дату-время. Например, в моем случае мне пришлось выполнить проверку того, произошла ли дата, хранящаяся в 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();
}
}
// (...)
}
В тестовом классе создайте объект 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().
В этом примере показано, как объединить 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
}
}
Мне нужен экземпляр 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
С помощью 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
}