programing

Java 8의 java.time API에서의 모킹 타임

copysource 2022. 10. 31. 23:31
반응형

Java 8의 java.time API에서의 모킹 타임

Joda Time은 DateTimeUtils.setCurrentMillisFixed()가 좋은 모의 시간을 가지고 있습니다.

그것은 시험에서 매우 실용적이다.

Java 8의 java.time API에 동등한 것이 있습니까?

가장 가까운 것은Clock물건.원하는 시간(또는 시스템 현재 시간)을 사용하여 Clock 개체를 만들 수 있습니다.모든 date.time 개체가 오버로드되었습니다.now현재 시간 대신 클럭오브젝트를 사용하는 메서드입니다.따라서 종속성 주입을 사용하여 특정 시간으로 클럭을 주입할 수 있습니다.

public class MyBean {
    private Clock clock;  // dependency inject
    ...
    public void process(LocalDate eventDate) {
      if (eventDate.isBefore(LocalDate.now(clock)) {
        ...
      }
    }
  }

자세한 내용은 JavaDoc 클럭을 참조하십시오.

새 클래스를 사용하여 새 클래스를 숨겼습니다.Clock.fixed테스트 작성 및 심플화:

public class TimeMachine {

    private static Clock clock = Clock.systemDefaultZone();
    private static ZoneId zoneId = ZoneId.systemDefault();

    public static LocalDateTime now() {
        return LocalDateTime.now(getClock());
    }

    public static void useFixedClockAt(LocalDateTime date){
        clock = Clock.fixed(date.atZone(zoneId).toInstant(), zoneId);
    }

    public static void useSystemDefaultZoneClock(){
        clock = Clock.systemDefaultZone();
    }

    private static Clock getClock() {
        return clock ;
    }
}
public class MyClass {

    public void doSomethingWithTime() {
        LocalDateTime now = TimeMachine.now();
        ...
    }
}
@Test
public void test() {
    LocalDateTime twoWeeksAgo = LocalDateTime.now().minusWeeks(2);

    MyClass myClass = new MyClass();

    TimeMachine.useFixedClockAt(twoWeeksAgo);
    myClass.doSomethingWithTime();

    TimeMachine.useSystemDefaultZoneClock();
    myClass.doSomethingWithTime();

    ...
}

밭을 이용했다.

private Clock clock;

그리고 나서.

LocalDate.now(clock);

내 프로덕션 코드에 있습니다.그런 다음 장치 테스트에서 Mockito를 사용하여 Clock.fixed()를 사용하여 클럭을 시뮬레이션했습니다.

@Mock
private Clock clock;
private Clock fixedClock;

조롱:

fixedClock = Clock.fixed(Instant.now(), ZoneId.systemDefault());
doReturn(fixedClock.instant()).when(clock).instant();
doReturn(fixedClock.getZone()).when(clock).getZone();

어설션:

assertThat(expectedLocalDateTime, is(LocalDate.now(fixedClock)));

사용하고 있다 Clock제품 코드를 혼란스럽게 합니다.

JMockit 또는 PowerMock을 사용하여 테스트 코드의 정적 메서드 호출을 모의할 수 있습니다.JMockit의 예:

@Test
public void testSth() {
  LocalDate today = LocalDate.of(2000, 6, 1);

  new Expectations(LocalDate.class) {{
      LocalDate.now(); result = today;
  }};

  Assert.assertEquals(LocalDate.now(), today);
}

편집: 비슷한 질문에 대한 Jon Sket의 코멘트를 읽은 후, 나는 과거의 내 자신과는 반대한다.무엇보다도 이 주장은 정적 방법을 조롱할 때 테스트를 가감할 수 없다는 것을 확신시켰다.

그러나 레거시 코드를 처리해야 하는 경우에는 정적 모킹도 사용할 수 있습니다.

조금 늦었지만, 여기 제가 시간을 조롱할 때 사용하는 것이 있습니다.java.dateKotlin API:

val now = LocalDate.of(2021, Month.FEBRUARY, 19)
val clock = Clock.fixed(Instant.ofEpochSecond(
    now.atStartOfDay().toEpochSecond(ZoneOffset.UTC)
), ZoneId.systemDefault())

그리고 나서 당신은 당신의 시계를 시험하기 위해 반에 넘길 수 있습니다.

val classToTest = MyClass(clock)

물론 테스트 가능 클래스에서는 클럭을 사용하여 날짜 또는 시간을 검색합니다.

class MyClass(private val clock: Clock = Clock.systemDefaultZone()) {
    // ...
    fun doSomething() = LocalDate.now(clock)...

필요합니다LocalDate대신 인스턴스LocalDateTime.
이러한 이유로 다음과 같은 유틸리티 클래스를 만들었습니다.

public final class Clock {
    private static long time;

    private Clock() {
    }

    public static void setCurrentDate(LocalDate date) {
        Clock.time = date.toEpochDay();
    }

    public static LocalDate getCurrentDate() {
        return LocalDate.ofEpochDay(getDateMillis());
    }

    public static void resetDate() {
        Clock.time = 0;
    }

    private static long getDateMillis() {
        return (time == 0 ? LocalDate.now().toEpochDay() : time);
    }
}

그 용도는 다음과 같습니다.

class ClockDemo {
    public static void main(String[] args) {
        System.out.println(Clock.getCurrentDate());

        Clock.setCurrentDate(LocalDate.of(1998, 12, 12));
        System.out.println(Clock.getCurrentDate());

        Clock.resetDate();
        System.out.println(Clock.getCurrentDate());
    }
}

출력:

2019-01-03
1998-12-12
2019-01-03

모든 생성 교체LocalDate.now()로.Clock.getCurrentDate()프로젝트 중.

스프링 부트 어플리케이션이기 때문입니다.전에test프로파일 실행은 모든 테스트에 대해 사전 정의된 날짜를 설정합니다.

public class TestProfileConfigurer implements ApplicationListener<ApplicationPreparedEvent> {
    private static final LocalDate TEST_DATE_MOCK = LocalDate.of(...);

    @Override
    public void onApplicationEvent(ApplicationPreparedEvent event) {
        ConfigurableEnvironment environment = event.getApplicationContext().getEnvironment();
        if (environment.acceptsProfiles(Profiles.of("test"))) {
            Clock.setCurrentDate(TEST_DATE_MOCK);
        }
    }
}

그리고 봄을 더한다.팩토리:

org.springframework.springfrackApplicationListener=com.init.Test Profile Configurer

다음은 EasyMock을 사용하여 Java 8 웹 응용 프로그램에서 JUnit 테스트를 위해 현재 시스템 시간을 특정 날짜로 재정의하는 작업 방법입니다.

Joda Time은 정말 좋지만(고마워 Stephen, Brian, 네가 우리 세상을 더 좋게 만들어줘서) 나는 그것을 사용할 수 없었다.

몇 가지 실험 후, 저는 결국 EasyMock을 사용하여 Java 8의 java.time API에서 특정 날짜까지 시간을 조롱하는 방법을 생각해냈습니다.

  • Joda Time API 사용 안 함
  • Power Mock 미포함.

필요한 것은 다음과 같습니다.

테스트된 수업에서 해야 할 일

순서 1

" " " 를 추가합니다.java.time.Clock된 클래스에 MyService인스턴스화 블록 또는 컨스트럭터를 사용하여 새 속성이 기본값으로 올바르게 초기화되는지 확인합니다.

import java.time.Clock;
import java.time.LocalDateTime;

public class MyService {
  // (...)
  private Clock clock;
  public Clock getClock() { return clock; }
  public void setClock(Clock newClock) { clock = newClock; }

  public void initDefaultClock() {
    setClock(
      Clock.system(
        Clock.systemDefaultZone().getZone() 
        // You can just as well use
        // java.util.TimeZone.getDefault().toZoneId() instead
      )
    );
  }
  { initDefaultClock(); } // initialisation in an instantiation block, but 
                          // it can be done in a constructor just as well
  // (...)
}

순서 2

Atribute 「」를 합니다.clock현재 날짜 시간을 호출하는 메서드로 변환합니다.를 들어,저 같은 에 저장된 를, 내우 stored stored stored for for for for for for for for for for for for for for for for for for for for for a for a for a a a a for for for a a a a a for for for for for for for for for for for for for for for for for for for for for for for for for for forLocalDateTime.now() 제가 체체니다 with대 , , , , , 。LocalDateTime.now(clock)다음과 같이 합니다.

import java.time.Clock;
import java.time.LocalDateTime;

public class MyService {
  // (...)
  protected void doExecute() {
    LocalDateTime dateToBeCompared = someLogic.whichReturns().aDate().fromDB();
    while (dateToBeCompared.isBefore(LocalDateTime.now(clock))) {
      someOtherLogic();
    }
  }
  // (...) 
}

테스트 수업에서 해야 할 일

순서 3

된 메서드를 클래스의 합니다.doExecute()바로

import java.time.Clock;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import org.junit.Test;

public class MyServiceTest {
  // (...)
  private int year = 2017;  // Be this a specific 
  private int month = 2;    // date we need 
  private int day = 3;      // to simulate.

  @Test
  public void doExecuteTest() throws Exception {
    // (...) EasyMock stuff like mock(..), expect(..), replay(..) and whatnot
 
    MyService myService = new MyService();
    Clock mockClock =
      Clock.fixed(
        LocalDateTime.of(year, month, day, 0, 0).toInstant(OffsetDateTime.now().getOffset()),
        Clock.systemDefaultZone().getZone() // or java.util.TimeZone.getDefault().toZoneId()
      );
    myService.setClock(mockClock); // set it before calling the tested method
 
    myService.doExecute(); // calling tested method 

    myService.initDefaultClock(); // reset the clock to default right afterwards with our own previously created method

    // (...) remaining EasyMock stuff: verify(..) and assertEquals(..)
    }
  }

2월 3일에 을 알 수 .myService하여 비교한 후 가 현재 되었습니다.initDefaultClock().

하였습니다.java.time.Clock에 의존하여

testImplementation("org.mockito:mockito-core")
testImplementation("org.mockito:mockito-inline")

는 ""를 합니다.Clock테스트에서 조롱당할 필드입니다.

@Service
public class TimeTestWithDateService {
    private final Clock clock = Clock.systemUTC();

    public TimeTest plan(UUID orderId) {
        return TimeTest.builder()
                .id(UUID.randomUUID())
                .orderId(orderId)
                .createdAt(ZonedDateTime.now(clock))
                .plannedAt(ZonedDateTime.now(clock)
                        .plusDays(1)
                        .withHour(8)
                        .truncatedTo(ChronoUnit.HOURS))
                .build();
    }

    public TimeTest ship(TimeTest timeTest) {
        return TimeTest.builder()
                .id(timeTest.getId())
                .orderId(timeTest.getOrderId())
                .createdAt(timeTest.getCreatedAt())
                .shippedAt(ZonedDateTime.now(clock))
                .build();
    }
}

@Value
@Builder
public class TimeTest {
    private UUID id;
    private UUID orderId;
    private ZonedDateTime createdAt;
    private ZonedDateTime plannedAt;
    private ZonedDateTime shippedAt;
}

에서는 「」를 합니다.Mockito.mockStaticClock.

@SpringBootTest
public class TimeTestWithDateServiceTest {
    @Autowired
    private TimeTestWithDateService timeTestService;

    private static Clock clock;
    private static ZonedDateTime now;

    @BeforeAll
    static void setupClock() {
        clock = Clock.fixed(
                Instant.parse("2020-12-01T10:05:23.653Z"),
                ZoneId.of("Europe/Prague"));
        now = ZonedDateTime.now(clock);

        var clockMock = Mockito.mockStatic(Clock.class);
        clockMock.when(Clock::systemUTC).thenReturn(clock);
    }

    @Test
    void timeTest_is_planned() {
        var orderId = UUID.randomUUID();
        var timeTest = timeTestService.plan(orderId);

        var tomorrowAt8am = now.plusDays(1).withHour(8).truncatedTo(ChronoUnit.HOURS);

        assertAll(
                () -> assertThat(timeTest).isNotNull(),
                () -> assertThat(timeTest.getId()).isNotNull(),
                () -> assertThat(timeTest.getOrderId()).isEqualTo(orderId),
                () -> assertThat(timeTest.getCreatedAt()).isEqualTo(now),
                () -> assertThat(timeTest.getPlannedAt()).isEqualTo(tomorrowAt8am),
                () -> assertThat(timeTest.getShippedAt()).isNull()
        );
    }

    @Test
    void timeTest_is_shipped() {
        var timeTest = timeTestService.plan(UUID.randomUUID());
        var shipped = timeTestService.ship(timeTest);
        assertAll(
                () -> assertThat(shipped).isNotNull(),
                () -> assertThat(shipped.getId()).isEqualTo(timeTest.getId()),
                () -> assertThat(shipped.getOrderId()).isEqualTo(timeTest.getOrderId()),
                () -> assertThat(shipped.getCreatedAt()).isEqualTo(timeTest.getCreatedAt()),
                () -> assertThat(shipped.getShippedAt()).isEqualTo(now)
        );
    }
}

이 예에서는 Instant와 Local Time을 조합하는 방법도 보여 줍니다(변환에 관한 문제에 대한 자세한 설명).

시험 중인 수업

import java.time.Clock;
import java.time.LocalTime;

public class TimeMachine {

    private LocalTime from = LocalTime.MIDNIGHT;

    private LocalTime until = LocalTime.of(6, 0);

    private Clock clock = Clock.systemDefaultZone();

    public boolean isInInterval() {

        LocalTime now = LocalTime.now(clock);

        return now.isAfter(from) && now.isBefore(until);
    }

}

그루비 테스트

import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized

import java.time.Clock
import java.time.Instant

import static java.time.ZoneOffset.UTC
import static org.junit.runners.Parameterized.Parameters

@RunWith(Parameterized)
class TimeMachineTest {

    @Parameters(name = "{0} - {2}")
    static data() {
        [
            ["01:22:00", true,  "in interval"],
            ["23:59:59", false, "before"],
            ["06:01:00", false, "after"],
        ]*.toArray()
    }

    String time
    boolean expected

    TimeMachineTest(String time, boolean expected, String testName) {
        this.time = time
        this.expected = expected
    }

    @Test
    void test() {
        TimeMachine timeMachine = new TimeMachine()
        timeMachine.clock = Clock.fixed(Instant.parse("2010-01-01T${time}Z"), UTC)
        def result = timeMachine.isInInterval()
        assert result == expected
    }

}

을 받아 스프링 를 실시하면 Mockito를 할 수 .ZonedDateTime사항이 다음 사항이 필요합니다.

주석

테스트 클래스에서는 를 사용하는 서비스를 준비해야 합니다.ZonedDateTime.

@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(SpringRunner.class)
@PrepareForTest({EscalationService.class})
@SpringBootTest
public class TestEscalationCases {
  @Autowired
  private EscalationService escalationService;
  //...
}

테스트 케이스

테스트에서는 원하는 시간을 준비하고 메서드 호출에 응답하여 얻을 수 있습니다.

  @Test
  public void escalateOnMondayAt14() throws Exception {
    ZonedDateTime preparedTime = ZonedDateTime.now();
    preparedTime = preparedTime.with(DayOfWeek.MONDAY);
    preparedTime = preparedTime.withHour(14);
    PowerMockito.mockStatic(ZonedDateTime.class);
    PowerMockito.when(ZonedDateTime.now(ArgumentMatchers.any(ZoneId.class))).thenReturn(preparedTime);
    // ... Assertions 
}

jmockit 사용:

코드:

// Mocking time as 9am
final String mockTime = "09:00:00"
new MockUp<LocalTime>() {
       @Mock
       public LocalTime now() {
           return LocalTime.parse(mockTime);
       }
};

Import:

import mockit.MockUp;
import mockit.Mock;

의존관계:

<groupId>org.jmockit</groupId>
<artifactId>jmockit</artifactId>
<version>1.41</version>

정답은 다음과 같습니다.https://gabstory.com/70?category=933660

import com.nhaarman.mockitokotlin2.given
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.mockito.Mock
import org.mockito.Mockito.mockStatic
import org.mockito.junit.jupiter.MockitoExtension
import org.springframework.data.projection.ProjectionFactory
import org.springframework.data.projection.SpelAwareProxyProjectionFactory
import java.time.Clock
import java.time.ZonedDateTime

@ExtendWith(MockitoExtension::class)
class MyTest {
   private val clock = Clock.fixed(ZonedDateTime.parse("2021-10-25T00:00:00.000+09:00[Asia/Seoul]").toInstant(), SEOUL_ZONE_ID)
   
    @BeforeEach
    fun setup() {
        runCatching {
            val clockMock = mockStatic(Clock::class.java)
            clockMock.`when`<Clock>(Clock::systemDefaultZone).thenReturn(clock)
        }
    }
    
    @Test
    fun today(){
      assertEquals("2021-10-25T00:00+09:00[Asia/Seoul]", ZonedDateTime.now().toString())
    }
}

언급URL : https://stackoverflow.com/questions/24491260/mocking-time-in-java-8s-java-time-api

반응형