Время издевательства в 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>