Как я могу издеваться над java.time.LocalDate.now()
В моем тестовом примере мне нужен метод, чувствительный ко времени тестирования, в этом методе мы используем java 8 класс LocalDate, это не Joda.
Что я могу сделать, чтобы изменить время, когда я запускаю тест
12 ответов
В вашем коде замените LocalDate.now()
с LocalDate.now(clock);
,
Затем вы можете пройти Clock.systemDefaultZone()
для производства и фиксированные часы для тестирования.
Вы можете реорганизовать свой код, чтобы сделать его более удобным для тестирования, например, заменить все вызовы LocalDate.now()
с вызовом некоторого метода настраиваемого mockable нестатического класса.
Кроме того, вы можете использовать mockStatic PowerMock.
Здесь мы должны имитировать статический метод. Я использую следующую зависимость. Помните, что весь наш тестовый код должен быть в блоке try. Как только мы вызываем LocalDate.now() или LocalDate
<!-- https://mvnrepository.com/artifact/org.mockito/mockito-inline -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>3.11.0</version>
<scope>test</scope>
</dependency>
Код:
@Test
void verifykNonLeapYear() {
LocalDate currentLocalDate = LocalDate.of(2010, 2, 13);
try (MockedStatic<LocalDate> topDateTimeUtilMock = Mockito.mockStatic(LocalDate.class)) {
topDateTimeUtilMock.when(() -> LocalDate.now()).thenReturn(currentLocalDate);
assertThat(TopDateTimeUtil.numberOfDaysInCurrentYear(), is(365));
}
}
Если нам нужно смоделировать статические методы, такие как now(), мы можем использовать несколько альтернатив, таких как PowerMock:
@RunWith(PowerMockRunner.class)
@PrepareForTest({ LocalDateTime.class })
public class LocalDateTimeUnitTest {
@Test
public void givenLocalDateTimeMock_whenNow_thenGetFixedLocalDateTime() {
Clock clock = Clock.fixed(Instant.parse("2014-12-22T10:15:30.00Z"), ZoneId.of("UTC"));
LocalDateTime dateTime = LocalDateTime.now(clock);
mockStatic(LocalDateTime.class);
when(LocalDateTime.now()).thenReturn(dateTime);
String dateTimeExpected = "2014-12-22T10:15:30";
LocalDateTime now = LocalDateTime.now();
assertThat(now).isEqualTo(dateTimeExpected);
}
}
Или JMockit, действительно, с JMockit мы можем использовать класс MockUp:
@Test
public void givenLocalDateTimeWithJMock_whenNow_thenGetFixedLocalDateTime() {
Clock clock = Clock.fixed(Instant.parse("2014-12-21T10:15:30.00Z"), ZoneId.of("UTC"));
new MockUp<LocalDateTime>() {
@Mock
public LocalDateTime now() {
return LocalDateTime.now(clock);
}
};
String dateTimeExpected = "2014-12-21T10:15:30";
LocalDateTime now = LocalDateTime.now();
assertThat(now).isEqualTo(dateTimeExpected);
}
Или класс Ожидания:
@Test
public void givenLocalDateTimeWithExpectations_whenNow_thenGetFixedLocalDateTime() {
Clock clock = Clock.fixed(Instant.parse("2014-12-23T10:15:30.00Z"), ZoneId.of("UTC"));
LocalDateTime dateTimeExpected = LocalDateTime.now(clock);
new Expectations(LocalDateTime.class) {
{
LocalDateTime.now();
result = dateTimeExpected;
}
};
LocalDateTime now = LocalDateTime.now();
assertThat(now).isEqualTo(dateTimeExpected);
}
Мы можем найти больше примеров здесь.
Другой простой альтернативой является использование метода now() с фиксированным экземпляром Clock. Конечно, большинство классов в пакете java.time имеют метод now() с параметром Clock:
@Test
public void givenFixedClock_whenNow_thenGetFixedLocalDateTime() {
Clock clock = Clock.fixed(Instant.parse("2014-12-22T10:15:30.00Z"), ZoneId.of("UTC"));
String dateTimeExpected = "2014-12-22T10:15:30";
LocalDateTime dateTime = LocalDateTime.now(clock);
assertThat(dateTime).isEqualTo(dateTimeExpected);
}
Вы можете использовать поставщик внутри своего класса, который вы тестируете, чтобы передавать текущее время везде, где используется дата-время.
public Supplier<LocalDateTime> localDateTime = () -> LocalDateTime.now();
а в тестовом методе просто переопределите его значение, например:
myClassObj.localDateTime = () -> LocalDateTime.parse("2020-11-24T23:59:59.999");
Если вы здесь и используетеMockito
и Котлин, сделайте следующее:
mockStatic(LocalDate::class.java, Mockito.CALLS_REAL_METHODS).use {
`when`(LocalDate.now()).thenReturn(date)
// Do your stuff here
}
Если вы используете Java, ознакомьтесь с этим выпуском , чтобы узнать, как это делается.
Вы можете издеваться над окончательными классами с помощьюMockito
. Добавь это'mockito-extensions'
каталог на вашsrc/test/resources
то естьsrc/test/resources/mockito-extensions
Добавить этот файл
org.mockito.plugins.MockMaker
с содержанием
mock-maker-inline
Mockito проверит каталог расширений на наличие файлов конфигурации при его загрузке. Этот файл позволит имитировать окончательные методы и классы.
Вы можете найти более подробную информацию об этом подходе, используя baeldung
Другой программный подход используетMockMakers.INLINE
в вашем коде, как показано в официальном примере :
Mockito.mock(ClassWithFinalMethod.class, withSettings().mockMaker(MockMakers.INLINE));
Mockito.when(inlineMock.finalMethodCallingNonFinal()).thenReturn("MOCKED");
assertEquals("MOCKED", inlineMock.finalMethodCallingNonFinal());
Вы также можете использовать аннотацию, как описано в документах :
@Mock(mockMaker = MockMakers.INLINE)
Foo mock;
Вы также можете захотеть передать фиксированные часы в производство (значение которых фиксировано в начале транзакции), чтобы избежать использования непоследовательного "сейчас" в различных объектах и запросах. Смотрите этот вопрос для деталей.
Используя Spring:
ClockConfiguration класс:
@Configuration
public class ClockConfiguration {
private final static LocalDate LOCAL_DATE = LocalDate.of(2019, 12, 17);
@Bean
@ConditionalOnMissingBean
Clock getSystemDefaultZoneClock() {
return Clock.systemDefaultZone();
}
@Bean
@Profile("test")
@Primary
Clock getFixedClock() {
return Clock.fixed(LOCAL_DATE.atStartOfDay(ZoneId.systemDefault()).toInstant(), ZoneId.systemDefault());
}
}
SomeService класс:
@Service
@RequiredArgsConstructor
public class SomeService {
private final Clock clock;
public void someMethod(){
...
LocalDateTime.now(clock)
LocalDate.now(clock)
...
}
}
У вас должен быть активный "тестовый" профиль в тесте:
SomeServiceTest класс:
@ActiveProfiles("test")
@EnableConfigurationProperties
@SpringBootTest(classes = [YourAppMainClass])
class SomeServiceTest {
...
}
чтобы Mock LocalDate до желаемой даты
@Test
public void mockStaticMethod() {
//dummy data
try(MockedStatic<LocalDate> mockedStatic=Mockito.mockStatic(LocalDate.class,Mockito.CALLS_REAL_METHODS)){
LocalDate currentDate=LocalDate.of(2023, 1, 11);
mockedStatic.when(LocalDate::now).thenReturn(currentDate);
//yourService.serviceMethod(arguments);
//assertEquals(expected, actual);
}
}
учитывая, что у меня есть случайный класс с методом, который просто возвращаетLocalDate.now()
import java.time.LocalDate;
public RandomClass {
public LocalDate getTodaysDate() {
return LocalDate.now();
}
}
и я хочу посмеяться над этим, чтобы вместо этого вернуть свой день рождения
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import java.time.LocalDate;
import java.time.Month;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class RandomClassTest {
private RandomClass randomClass;
private MockedStatic<LocalDate> localDateMockedStatic;
@BeforeEach
void setUp() {
randomClass = new RandomClass();
// instead of using try-with resources, i decide to initialize the mockedStatic object before each test method
localDateMockedStatic = Mockito.mockStatic(LocalDate.class, Mockito.CALLS_REAL_METHODS);
}
@AfterEach
void tearDown() {
localDateMockedStatic.reset();
localDateMockedStatic.close(); // and close the mockedStatic after each test method
}
@Test
public void getTodaysDateBirthdayTest() {
LocalDate birthday = LocalDate.of(1999, Month.SEPTEMBER, 29);
localDateMockedStatic.when(LocalDate::now).thenReturn(birthday);
assertEquals(birthday, randomClass.getTodaysDate());
}
@Test
public void getTodaysDateDefaultTest() {
// due to Mockito.CALLS_REAL_METHODS, this has default functionality
assertEquals(LocalDate.now(), randomClass.getTodaysDate());
}
}
по сути это то же самое, что и некоторые другие ответы в этой теме, но мне это кажется более приятным визуально, поэтому мне нравится это делать
Вы можете просто передать текущую дату как параметр функции.
fun doSmthng(now: LocalDate = LocalDate.now()) {
testAnotherService.doSmthng1(TestObj("my message!", now))
}
А в тесте вы можете передать какую-то конкретную дату и утверждать ее. Идея состоит в том, чтобы внедрить зависимости вместо того, чтобы явно создавать в теле функции.