D-09: DateProvider パターン(テスト容易な日付注入)
ビジネスロジックの中で LocalDate.now() を直接呼ぶと、 「今日が増税日の前日のときに正しく動くか」「年度末判定が 3/31 で正しく true になるか」といったテストが書けません。 DateProvider インターフェースを DI(依存性注入)することで、テスト時に任意の日付を注入できるようにします。
いつ使うか
- 消費税率・キャンペーン適用期間・有効期限など、「今日の日付」に依存するビジネスロジックをテストしたいとき
- バッチ処理で「処理基準日」を設定ファイルから注入してテストしたいとき
- 日付処理を含むクラスの単体テストで、特定の日付を固定して境界値テストを行いたいとき
問題のあるコード vs DateProvider パターン
❌ テストしにくいコード
public int getTaxRate() {
// now() を直接呼ぶとテスト不可
LocalDate today = LocalDate.now();
if (!today.isBefore(
LocalDate.of(2019, 10, 1))) {
return 10;
}
return 8;
}✅ DateProvider パターン
public int getTaxRate() {
// dateProvider 経由で取得
LocalDate today =
dateProvider.getToday();
if (!today.isBefore(
LocalDate.of(2019, 10, 1))) {
return 10;
}
return 8;
}サンプルコード
Java 8 では FixedDateProvider をクラスで実装します。フィールドに fixedDate と fixedDateTime を持ち、コンストラクターで両方を初期化します。
よくあるミス・注意点
⚠️ LocalDate.now() を直接呼ぶとテストができない
ビジネスロジックの中で直接 LocalDate.now() を呼ぶと、テストを実行する「今日の日付」に依存してしまいます。 たとえば 2019/10/01 より前に実装してテストが通っていたコードが、 増税後の日付でテストを実行すると失敗する可能性があります。 DateProvider を DI することで、テストで任意の日付を注入できるようになります。
⚠️ ConfigurableDateProvider の null 処理に注意
設定ファイルに日付が設定されていない場合(null または空文字)は実時刻を使うという設計にすると、 本番環境で意図せず固定日付が有効になるリスクを避けられます。 ただし、null を許容する設計は NullPointerException の原因にもなるため、 初期化時に状態を確定させ、getToday() / getNow() 内では null チェックを確実に行いましょう。
⚠️ Java 17 の record は不変オブジェクト
record FixedDateProvider(LocalDate fixedDate) はフィールドが final で変更できません。一度生成したら日付を変えることはできないため、 テストケースごとに新しいインスタンスを生成してください。 これは record の特性を活かした正しい使い方です。
テストする観点
- 増税前日(2019/9/30)で getTaxRate() が 8 を返すこと(境界値)
- 増税当日(2019/10/1)で getTaxRate() が 10 を返すこと(境界値)
- 年度末(3/31)で isEndOfFiscalYear() が true を返すこと
- 年度末以外(3/30、4/1)で isEndOfFiscalYear() が false を返すこと(境界値)
- ConfigurableDateProvider に有効な日付文字列を渡したとき、その日付が返ること
- ConfigurableDateProvider に null または空文字を渡したとき、システム日時が返ること