Javaでテストで日時を差し替える

2023/11/09に公開

LocalDate.now()などを利用するときは以下のようなクラスを経由し

package org.SystemDate;

import java.time.*;

public class SystemDate {
    static Clock clock = Clock.systemDefaultZone();

    public static YearMonth currentMonth() {
        return YearMonth.now();
    }

    public static LocalDate today() {
        return LocalDate.now(clock);
    }

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

    public static LocalTime time() {
        return LocalTime.now(clock);
    }
}

テストでは以下のように利用する

package org.SystemDate;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.time.LocalDate;

public class SystemDateTest {

    @BeforeEach
    void setUp() {
        // LocalDate.now()で返ってくる値を指定する
        TestDateUtil.setup(LocalDate.of(2023, 11, 10));
    }

    @Test
    void テスト日付を固定できる() {
        LocalDate today = SystemDate.today();
        Assertions.assertEquals(LocalDate.of(2023, 11, 10), today);
    }

}
package org.SystemDate;

import java.lang.reflect.Field;
import java.time.*;

public class TestDateUtil {
    public static void setup(LocalDate localDate) {
        setup(LocalDateTime.of(localDate, LocalTime.now()));
    }

    public static void setup(LocalDate localDate, LocalTime localTime) {
        setup(LocalDateTime.of(localDate, localTime));
    }

    public static void setup(LocalDateTime localDateTime) {
        try {
            Class<?> clazz = SystemDate.class;

            //SystemDateクラスのフィールド(メンバー変数)であるclockのFieldを取得
            Field clock = clazz.getDeclaredField("clock");

            // clockフィールドに対してアクセス制限を外す
            clock.setAccessible(true);

            // clockフィールドに対して、第二引数を設定する。第一引数はstaticの場合利用しないのでnull。
            clock.set(null, Clock.fixed(localDateTime.toInstant(ZoneOffset.ofHours(9)), ZoneId.systemDefault()));
        } catch (Exception ex) {
            throw new IllegalStateException(ex);
        }
    }

    public static void clear() {
        try {
            Class<?> clazz = SystemDate.class;

            Field clock = clazz.getDeclaredField("clock");

            clock.setAccessible(true);
            clock.set(null, Clock.systemDefaultZone());
        } catch (Exception ex) {
            throw new IllegalStateException(ex);
        }
    }

}

これでテストコード側が好きな日時にできる。

Discussion