junit 模拟Instant.now(),但不使用Clock构造函数或Clock对象

dpiehjr4  于 12个月前  发布在  其他
关注(0)|答案(4)|浏览(112)

我有下面的代码在我的一个方法

ZonedDateTime current = Instant.now().atZone(ZoneId.of(AMERICA_NEW_YORK));

我想在JUnit测试中模拟current
我尝试使用java.time.Clock,但为此,我需要将其添加到类构造函数中,因为我的代码是编写到旧版本的Spring中的,并且使用基于XML的配置此类会导致问题,因为如果我使用Clock的构造函数,则需要application-context.xml文件中的构造函数参数。
有没有什么方法可以避免在上面的代码中配置构造函数和mock current

更新

根据Pavel Smirnov的评论,我在下面尝试了,但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());
mpgws1up

mpgws1up1#

基于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)) {...}
sz81bmfz

sz81bmfz2#

你可以声明一个返回ZoneDateTime的函数:

public ZoneDateTime getCurrentTime () {
    return Instant.now().atZone(ZoneId.of(AMERICA_NEW_YORK));
}

并将该函数的结果分配给current字段:

ZonedDateTime current = getCurrentTime();

现在你可以简单地使用Mockito framework将其替换为所需的值:

doReturn(yourValue).when(yourObject).getCurrentTime();
bxfogqkk

bxfogqkk3#

使用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!");
    }
}
vjhs03f7

vjhs03f74#

这里有一个与Java 17一起工作的解决方案,利用Mockito的静态mocking及其CALLS_REAL_METHODS选项来避免mocking所有Instant的静态方法。

var clock = Clock.fixed(Instant.now().minus(10, ChronoUnit.MINUTES), ZoneOffset.UTC);
ClockUtil.setClock(clock);
var mockedInstant = Instant.now(clock);
MockedStatic<Instant> mockedStatic = mockStatic(Instant.class, Mockito.CALLS_REAL_METHODS);
mockedStatic.when(Instant::now).thenReturn(mockedInstant);

相关问题