Scala

Scalaでテストのときだけタイムスタンプを固定値にする

ちきさんです。

最近Scalaのコードをがっつり触ることがあり、テストのときはタイムスタンプを固定値にしたいなと思い、よくわからないので色々調べたらわかったのでメモを残します。

やりたいこと

JavaScriptでこんな風に関数を書き換えられるけど同じようなことをやりたい。

Date.now = () => 1;

Date.now() === 1; // => true

Joda-Timeとかいうのを使うらしい

http://www.joda.org/joda-time/ をScalaで使えるようにしたものが https://github.com/nscala-time/nscala-time なのでこれのREADMEを参考にして依存関係を設定します。

そしてファイルのimport文で下記のように書くと使えるようになります。

import org.joda.time.{DateTime, DateTimeUtils}

Joda-TimeのDateTimeは簡単にモックできる

このように書くだけでした。

DateTimeUtils.setCurrentMillisFixed(12345)

注意点としては、あくまでJoda-TimeのDateTimeがモックの範囲内なのでSystem.currentTimeMillis()などはモックされないということです。以下にまとめました。

  • モックされるもの
    • DateTime.now().getMillis(), DateTimeUtils.currentTimeMillis()
  • モックされないもの
    • System.currentTimeMillis()

テストコードの例

実際にテストコードを書くとこんな感じになります。タイムスタンプを扱うときはJoda-Timeを使っておけば安心できそうですね。

DateTimeMockSpec.scala
import org.joda.time.{DateTime, DateTimeUtils}
import org.scalatest.{BeforeAndAfter, FunSpec}

class DateTimeMockSpec extends FunSpec with BeforeAndAfter {
  val fixedMillis: Long = System.currentTimeMillis()

  before {
    // beforeの中でモックすることで各テストに反映される。
    DateTimeUtils.setCurrentMillisFixed(fixedMillis)
  }

  after {
    // afterの中で元に戻さないと次のテストにも反映されてしまう。
    DateTimeUtils.setCurrentMillisSystem()
  }

  describe("DateTimeMock") {
    it("DateTime.now().getMillis()はモックされる") {
      assert(DateTime.now().getMillis() == fixedMillis)
    }

    it("DateTimeUtils.currentTimeMillis()はモックされる") {
      assert(DateTimeUtils.currentTimeMillis() == fixedMillis)
    }

    it("System.currentTimeMillis()はモックされない") {
      assert(System.currentTimeMillis() != fixedMillis)
    }
  }
}

まとめ

普段からJoda-Timeを使って現在時刻を表すようにしよう。


補足

beforeafterは一つのClassにつき一回しか呼び出すことができず、どこに書いても(ネストされたdescribeの中に書いたとしても) そのClass内の全てのテスト(it)の前後で実行されます。

JavaScript脳には??という感じですがそういうものらしいです。