概要
消費税率の切り替え日、年度末判定、キャンペーン期間のチェックなど、業務ロジックは「今日の日付」に依存する処理が多くあります。しかし LocalDate.now() をロジック内で直接呼ぶと、テスト時に日付を固定できず、境界値の検証が難しくなります。DateProvider インターフェースを導入して日付の取得元を DI で差し替えられるようにすれば、テストでは固定日付を注入し、本番ではシステム日時を使うという切り分けが自然にできます。この記事では、インターフェース設計、プロダクション実装、テスト用の固定日付プロバイダーを Java 標準 API だけで実装します。
使いどころ
消費税率の変更日(2019/10/1)の前後でロジックの動作を単体テストで検証する
年度末判定(3月31日)のテストを任意の日付で実行できるようにする
環境設定で日付を固定し、過去日付での動作確認やデモ用途に対応する
コード例
import java.time.LocalDate;
public class DateProviderDemo {
interface DateProvider {
LocalDate getToday();
LocalDateTime getNow();
}
static class SystemDateProvider implements DateProvider {
@Override
public LocalDate getToday() {
return LocalDate.now();
}
@Override
public LocalDateTime getNow() {
return LocalDateTime.now();
}
}
record FixedDateProvider(LocalDate fixedDate)
implements DateProvider {
@Override
public LocalDate getToday() { return fixedDate; }
@Override
public LocalDateTime getNow() {
return fixedDate.atStartOfDay();
}
}
static class TaxRateService {
private final DateProvider dateProvider;
TaxRateService(DateProvider dateProvider) {
this.dateProvider = dateProvider;
}
public int getTaxRate() {
var today = dateProvider.getToday();
var boundary = LocalDate.of(2019, 10, 1);
return !today.isBefore(boundary) ? 10 : 8;
}
}
public static void main(String[] args) {
var prod = new TaxRateService(new SystemDateProvider());
System.out.println("現在の税率: " + prod.getTaxRate() + "%");
var before = new TaxRateService(
new FixedDateProvider(LocalDate.of(2019, 9, 30)));
System.out.println("2019/9/30: " + before.getTaxRate() + "%");
var after = new TaxRateService(
new FixedDateProvider(LocalDate.of(2019, 10, 1)));
System.out.println("2019/10/1: " + after.getTaxRate() + "%");
}
}Version Coverage
record を使えばテスト用 FixedDateProvider を1行で定義できる。var と組み合わせてテストコードも簡潔になる。
// Java 17: record で簡潔に定義
record FixedDateProvider(LocalDate fixedDate)
implements DateProvider {
@Override
public LocalDate getToday() { return fixedDate; }
@Override
public LocalDateTime getNow() {
return fixedDate.atStartOfDay();
}
}Library Comparison
注意点
DateProvider の注入を忘れて LocalDate.now() を直接呼ぶメソッドが混在すると、テストの再現性が崩れる。規約で統一すること
テスト用の FixedDateProvider で時刻を固定する場合、atStartOfDay() で 00:00:00 になる点を意識する。時刻精度が必要なテストでは別途対応が必要
ConfigurableDateProvider のような設定ファイル連動型は、設定値の読み込みタイミングに注意。起動時に一度だけ読むか、毎回読むかで挙動が変わる
java.time.Clock を使う方法もあるが、インターフェースの方が呼び出し側の意図が明確になる場面が多い
FAQ
Clock は標準 API で手軽ですが、DateProvider の方がビジネスロジックとの分離が明確で、意図が伝わりやすいです。
可能です。SystemDateProvider を @Component で登録し、テスト時に @MockBean で FixedDateProvider に差し替えます。
日付に依存するサービスクラスのコンストラクタで受け取るのが一般的です。全メソッドに渡す必要はありません。