Как я могу издеваться над 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))
}

А в тесте вы можете передать какую-то конкретную дату и утверждать ее. Идея состоит в том, чтобы внедрить зависимости вместо того, чтобы явно создавать в теле функции.

Другие вопросы по тегам