Макет Instant.now () без использования Clock в конструкторе или без объекта Clock

#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() в одной из своих функций (например, чтобы определить, есть ли тайм-аут)?