Java で現在時刻を DI する

アウトプットに現在時刻を含むようなメソッドの単体テストをやろうとすると、テストの度に戻り値が異なるので値の検証ができない。
Java8 からは java.time.Clock を使って現在時刻を DI することができるようになった。
Spring フレームワークでは DI するオブジェクトを @Inject で指定できるが、Clock は抽象クラスなので @Inject するとインスタンス生成エラーとなってしまう。


「この問題をどうやって解決したものか」と一日頭を悩ませ、定時間際に解決したのでここにメモっておく。

  • java.time.Clock を継承したクラス SystemClock を作る。オーバーライドしたメソッドは Clock に丸投げ。
  • java.time.Clock を継承したクラス TestSystemClock を作る。Clock.fixed() で時刻の進まない固定時計を持たせる。オーバーライドしたメソッドは固定時計のものを返す。
  • テスト対象となるクラスでは Clock 型のメンバ変数 clock に @Injection を付ける。現在時刻は Date.from(clock.Instant()) で取得できる。
  • applicationContext.xml に SystemClock を bean(id=clock) として登録する。
  • applicationContextTest.xml に TestSystemClock を bean(id=clock) として登録する。
  • applicationContextTest → applicationContext の順に設定が読み込まれるようにテストを書いて実行する。

これがうまく動くと、テスト時は TestSystemClock による固定時計、それ以外は SystemClock による現在時刻が DI される。