#java #junit #java-8
#java #junit #java-8
Вопрос:
У меня есть приведенный ниже код в одном из моих методов
ZonedDateTime current = Instant.now().atZone(ZoneId.of(AMERICA_NEW_YORK));
Я хочу издеваться current
в тесте JUnit.
Я пытался с java.time.Clock
, но для этого мне нужно добавить его в конструктор класса, поскольку мой код написан в старых версиях Spring, и с использованием конфигурации на основе XML этот класс вызывает проблему, поскольку для него требуется аргумент конструктора в application-context.xml файл, если я использую конструктор с Clock
.
Есть ли какой-либо способ избежать настройки конструктора и макета current
в приведенном выше коде.
Обновить
Согласно комментариям Павла Смирнова, я попробовал ниже, но current
все еще возвращаю сегодняшнюю дату, но не ту, над которой я издеваюсь.
ZonedDateTime exactOneDay = ZonedDateTime.parse("Sun Oct 21 12:30:00 EDT 2018", Parser);
doReturn(exactOneDay).when(spyEmployeeHelper).getCurrentTime();
employee = getEmployees().get(0);
assertEquals(Integer.valueOf(1), employee.getNoticePeriod());
Комментарии:
1. Вы можете попробовать использовать PowerMockito и mockStatic для Instant.now()
2. Лучшим способом было бы провести рефакторинг вашего метода, чтобы извлечь эту строку в новый метод. Тогда вы можете легко смоделировать / подсмотреть этот метод.
3. Лучший способ — изменить конструктор и обновить
application-context.xml
соответствующим образом.4. забавно, что в вашем названии упоминается
mock
, но в вашем вопросе не упоминаются макетные фреймворки
Ответ №1:
Решение на основе Mockito, где код использует обычный Instant.now()
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import java.time.Clock;
import java.time.Instant;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
public class MockInstantTest {
private MockedStatic<Clock> clockMock;
@BeforeEach
public void setup() {
mockInstant(1640000000); // set desired return value 2021-12-20T11:33:20Z
}
@AfterEach
public void destroy() {
clockMock.close();
}
private void mockInstant(long expected) {
Clock spyClock = spy(Clock.class);
clockMock = mockStatic(Clock.class);
clockMock.when(Clock::systemUTC).thenReturn(spyClock);
when(spyClock.instant()).thenReturn(Instant.ofEpochSecond(expected));
}
@Test
void testWithMockedIstant() {
// invoking Instant.now() will always return the same value
assertThat(Instant.now().toString()).isEqualTo("2021-12-20T11:33:20Z");
}
}
Объяснено решение
Решение ссылается на тот факт, что Instant.now()
вызывает Clock.systemUTC().instant()
Clock
является абстрактным, поэтому мы отслеживаем нестатические методыclock.instant()
издевается, чтобы вернуть желаемое значениеClock.systemUTC()
является статическим, поэтому нам нужен mockStatic- для закрытия MockedStatic требуется использование
@Before
/@After
(в качестве альтернативы вы можете использоватьtry(MockedStatic<Clock> clockMock = mockStatic(Clock.class)) {...}
)
Комментарии:
1. Обязательно включите артефакт mockito-inline (вместо просто mockito-core ).
2. Больше не работает с JDK 17 (Eclipse Temurin 17.0.2.8). Хотя работал с JDK 11.
Ответ №2:
Вы можете объявить функцию, которая возвращает ZoneDateTime
:
public ZoneDateTime getCurrentTime () {
return Instant.now().atZone(ZoneId.of(AMERICA_NEW_YORK));
}
и присвоите результат этой функции current
полю:
ZonedDateTime current = getCurrentTime();
Теперь вы можете просто заменить его на желаемое значение, используя Mockito framework:
doReturn(yourValue).when(yourObject).getCurrentTime();
Комментарии:
1. Спасибо, Павел. Я пытаюсь тем же способом, но
current
все еще возвращаю сегодняшнюю дату, а не то, что я ожидал. Пожалуйста, посмотрите мой обновленный вопрос.2. @ppb, предоставленный вами фрагмент кода выглядит нормально. Можете ли вы показать полный код вашего теста? Как создать spyEmployeeHelper?
3. Я создаю spyEmployeeHelper
EmployeeHelper spyEmployeeHelper = mock(EmployeeHelper.class);
таким образом. Я также пробовалEmployeeHelper spyEmployeeHelper = spy(employeeHelperObject);
этим способом.4. assertEquals(Integer.valueOf(1), employee.getNoticePeriod()); Не следует ли вам вызывать метод getNoticePeriod() для вашего объекта spy вместо ’employee’? Проверьте в своем отладчике, для какого экземпляра является объектом ’employee’?
Ответ №3:
При использовании Mockito вы можете легко издеваться следующим образом:
ZoneId zoneId = ZoneId.of("America/New_York");
ZonedDateTime current = ZonedDateTime.now(zoneId);
Timestamp timestamp = Timestamp.from(Instant.now());
when(timestamp.toInstant()).thenReturn(Instant.from(current));
Добавление теста для тайм-аута, например:
@Test
public void testForTimeout() throws InterruptedException {
ZoneId zoneId = ZoneId.of("America/New_York");
ZonedDateTime current = ZonedDateTime.now(zoneId);
Timestamp timestampBeforeCall = Timestamp.from(Instant.now());
// Call Class.method() or here instead we just introduce an artificial wait time :
Thread.sleep(3000);
Timestamp timestampAfterCall = Timestamp.from(Instant.now());
long timeoutInMilliseconds = 2000;
long diff = timestampAfterCall.getTime() - timestampBeforeCall.getTime();
log.info(String.valueOf(diff));
if(diff > timeoutInMilliseconds) {
log.error("Call Timed Out!");
}
}
Комментарии:
1. Действительно приятно! Можете ли вы показать, как бы вы использовали его для тестирования класса, который, например, использует Instance.now() в одной из своих функций (например, чтобы определить, есть ли тайм-аут)?