なぜ「テスト用日時固定」ユーティリティが必要になるのか
日付・時間に依存するロジックは、業務システムのど真ん中にいます。
「締め切りを過ぎているか」「有効期限内か」「月末かどうか」「営業日かどうか」。
こういう処理をテストするときに、LocalDate.now() や Instant.now() をそのまま使っていると、テストがとてもやりづらくなります。
今日が何日かによってテスト結果が変わる。
月末のテストをしたいのに、実際のカレンダーが月末になるまで待たないといけない。
「1年後」「10年後」の挙動を確認したいのに、現実の時間を進めることはできない。
これを一気に解決する考え方が、「テスト用に日時を固定する」ユーティリティです。
“システムにとっての今”を、テストコード側から自由に決められるようにします。
基本の考え方は「Clock を固定する」
直接 now() を呼ぶのではなく、Clock を渡す
まず大前提として、業務ロジックの中で LocalDate.now() や Instant.now() を直接呼ばない、というルールを置きます。
代わりに、「Clock を受け取って、その Clock から“今”を取得する」ようにします。
例えば、「今日が締め切りを過ぎているか」を判定するクラスを考えます。
import java.time.Clock;
import java.time.LocalDate;
public class DeadlineService {
private final Clock clock;
public DeadlineService(Clock clock) {
this.clock = clock;
}
public boolean isExpired(LocalDate deadline) {
LocalDate today = LocalDate.now(clock);
return today.isAfter(deadline);
}
}
Java本番では、普通のシステム時計を渡します。
DeadlineService service = new DeadlineService(Clock.systemDefaultZone());
Javaここまでが「Clock差し替え」の土台です。
この土台があるからこそ、「テスト用日時固定」が生きてきます。
テスト用日時固定の主役:Clock.fixed
任意の日時を「今」として固定する
テストでやりたいことはシンプルです。
「今が 2025-03-31 の 10:00 だと仮定して、このロジックを動かしたい」。
これを実現してくれるのが Clock.fixed です。
import java.time.Clock;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
public class DeadlineServiceTest {
public static void main(String[] args) {
Instant fixedInstant = Instant.parse("2025-03-31T01:00:00Z");
ZoneId zone = ZoneId.of("Asia/Tokyo");
Clock fixedClock = Clock.fixed(fixedInstant, zone);
DeadlineService service = new DeadlineService(fixedClock);
LocalDate deadline1 = LocalDate.of(2025, 3, 30);
LocalDate deadline2 = LocalDate.of(2025, 3, 31);
LocalDate deadline3 = LocalDate.of(2025, 4, 1);
System.out.println(service.isExpired(deadline1)); // true を期待
System.out.println(service.isExpired(deadline2)); // 仕様に応じて true/false を確認
System.out.println(service.isExpired(deadline3)); // false を期待
}
}
Javaここで重要なのは、「テストコード側で“今”を完全にコントロールしている」ということです。
実際のカレンダーが何日であろうと、テストは常に「2025-03-31 の世界」で動きます。
「テスト用日時固定」ユーティリティをひとまとめにする
毎回 Clock.fixed を書かないための小さなヘルパー
テストコードのあちこちで Clock.fixed(Instant.parse(...), ZoneId.of(...)) と書くのは、少し面倒です。
そこで、「テスト用日時固定」を行う小さなユーティリティを用意しておくと便利です。
import java.time.Clock;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
public class TestClocks {
public static Clock fixedAt(LocalDateTime dateTime, ZoneId zone) {
Instant instant = dateTime.atZone(zone).toInstant();
return Clock.fixed(instant, zone);
}
public static Clock fixedAtTokyo(LocalDateTime dateTime) {
ZoneId tokyo = ZoneId.of("Asia/Tokyo");
return fixedAt(dateTime, tokyo);
}
}
Java使い方の例です。
import java.time.LocalDateTime;
public class DeadlineServiceTest2 {
public static void main(String[] args) {
Clock clock = TestClocks.fixedAtTokyo(
LocalDateTime.of(2025, 3, 31, 10, 0)
);
DeadlineService service = new DeadlineService(clock);
// 以降は同じ
}
}
Javaここで深掘りしたいのは、「テストコードの可読性」です。fixedAtTokyo(2025-03-31 10:00) のように書けると、
「このテストはどんな日時を前提にしているのか」が一目で分かります。
「時間が進むテスト」と「時間が止まったテスト」
fixed は「時間が止まった世界」を作る
Clock.fixed は、その名の通り「時間が止まった Clock」です。Instant.now(clock) を何度呼んでも、同じ値が返ってきます。
これは、「ある瞬間における判定」をテストするには最高ですが、
「1時間後」「1日後」の挙動をテストしたいときには少し工夫が必要です。
「時間を手動で進める」テスト用 Clock を作る
少し応用として、「テストコードから時間を進められる Clock」を自作することもできます。
初心者向けに、シンプルなイメージだけ示します。
import java.time.Clock;
import java.time.Instant;
import java.time.ZoneId;
public class MutableClock extends Clock {
private Instant current;
private final ZoneId zone;
public MutableClock(Instant initial, ZoneId zone) {
this.current = initial;
this.zone = zone;
}
@Override
public ZoneId getZone() {
return zone;
}
@Override
public Clock withZone(ZoneId zone) {
return new MutableClock(current, zone);
}
@Override
public Instant instant() {
return current;
}
public void plusSeconds(long seconds) {
current = current.plusSeconds(seconds);
}
}
Javaテストでは、こう使えます。
MutableClock clock = new MutableClock(
Instant.parse("2025-03-31T00:00:00Z"),
ZoneId.of("Asia/Tokyo")
);
DeadlineService service = new DeadlineService(clock);
// 0時の世界でテスト
// ...
clock.plusSeconds(3600); // 1時間進める
// 1時の世界でテスト
// ...
Javaここでのポイントは、「テスト用日時固定」をさらに発展させて、“テスト用時間操作”までできるようにしていることです。
本格的なシステムでは、こうしたテクニックがかなり効いてきます。
アプリ全体で「テスト用日時固定」をどう扱うか
共通の Clock プロバイダを持つ設計
以前の「Clock差し替え」と同じく、アプリ全体で使う Clock を一か所から提供する設計にしておくと、
テスト用日時固定がとても楽になります。
例えば、こんなクラスを用意します。
import java.time.Clock;
public class AppClock {
private static Clock clock = Clock.systemDefaultZone();
public static Clock get() {
return clock;
}
public static void set(Clock newClock) {
clock = newClock;
}
public static void reset() {
clock = Clock.systemDefaultZone();
}
}
Java業務コード側では、こう書きます。
import java.time.LocalDate;
public class SomeService {
public LocalDate today() {
return LocalDate.now(AppClock.get());
}
}
Javaテストでは、次のように「テスト用日時固定」を行えます。
AppClock.set(TestClocks.fixedAtTokyo(
LocalDateTime.of(2025, 3, 31, 10, 0)
));
// テスト実行
AppClock.reset(); // 終わったら元に戻す
Javaここで深掘りしたいのは、「テスト用日時固定を“アプリ全体の設定”として扱う」という感覚です。
これにより、「このテストスイートは全部 2025-03-31 の世界で動かす」といったことが簡単にできます。
セキュリティ・運用の観点から見た「テスト用日時固定」
「未来日」「過去日」のバグを事前に潰せる
有効期限、長期保存、ログの保持期間、契約期間など、
日付・時間に関するバグは、運用を続けてから初めて表面化することが多いです。
テスト用日時固定をきちんと用意しておけば、
「1年後」「5年後」「有効期限ギリギリ」「期限切れ直後」といった状態を、
テスト環境で安全に再現できます。
これは、「本番でしか起きない日付バグ」を事前に潰すための、かなり強力な武器です。
「システム時刻の変更」に依存しないテスト
運用上、サーバの時刻をいじることは極力避けるべきです。
テストのために本番相当環境の時刻を変える、というのは論外です。
テスト用日時固定ユーティリティを使えば、
サーバの時刻を一切いじらずに、「任意の日時の世界」をテスト内だけで作れます。
これは、セキュリティ・運用の観点から見ても、とても健全なやり方です。
まとめ:テスト用日時固定ユーティリティで身につけてほしい感覚
テスト用日時固定ユーティリティは、「システムにとっての“今”を、テストコード側から自由に決める」ための仕組みです。
その中心にあるのは、次のような考え方です。
業務ロジックは now() を直接呼ばず、必ず Clock を受け取る。
テストでは Clock.fixed や自作のテスト用 Clock を使って、「今」を固定・操作する。
共通の Clock プロバイダ(AppClock など)を用意し、アプリ全体で一貫した“今”を扱う。
未来日・過去日・境界日(締め切り日・月末など)を、テストで何度でも再現できるようにする。
もしあなたのテストコードが、「今日が何日かによって成功したり失敗したりする」状態になっているなら、
それはまさに「テスト用日時固定」を導入するサインです。
一度、日付・時間に依存するクラスを洗い出して、
「Clock を受け取るように書き換えられないか」「テスト用 Clock を差し込めないか」を眺めてみてください。
そこから先は、テストが安定し、将来の運用年数を見据えた“強い”日付・時間設計に、自然とつながっていきます。

