JUnitでシステム日付けを固定する方法


システム日付けを固定したい

仕事で単体試験フェーズになった時、日付けを固定したいという場面はよくでてくる。
今回はMockライブラリを用いて時間を固定してみた。

前提 テスト対象のクラスは??

今回は下記をテストするJUnitを記述することにする。

public class OutputDate {
    public void getDate(){
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 hh時mm分ss秒SSSミリ秒");
        System.out.println(sdf.format(new Date()));
    }
}

上手くいけばnew Date()で出力される日時が固定されるはず!

まずはPowerMock!

上司に教えてもらい下記のような方法で実現できることを知った。

@RunWith(PowerMockRunner.class)
@PrepareForTest({OutputDate.class, Date.class})
public class FixSysDateUsingPowerMock1 {

    OutputDate output = new OutputDate();

    @Before
    public void setUp(){
        setDatemock();
    }


    @Test
    public void test() {
        output.getDate();
    }

    /**
     * システム日付けを固定する
     * この方法だとミリ秒まで固定できない
     */
    private static void setDatemock(){
        Calendar cal = Calendar.getInstance();
        //時間を2018年1月1日10時10分10秒にセットする(月は0が1月)
        cal.set(2018, 0,1,10,10,10);
        try {
            PowerMockito.whenNew(Date.class).withNoArguments().thenReturn(cal.getTime());
            when(new Date()).thenReturn(cal.getTime());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

setDatemock()メソッドでCalendarクラスのset()メソッドを用いて固定化を試みている方法である。最初はこれでいいやと思ったのだが、これだとミリ秒を固定できない。setメソッドでミリ秒まで指定するものが無いから(自分が探した範囲では)である。

PowerMockでミリ秒まで出したい、、

どうしようか悩んでいたところ、文字列でうまくできないかな?という上司からのヒントのもと、以下の方法を思いついた。


@RunWith(PowerMockRunner.class)
@PrepareForTest({ OutputDate.class, Date.class })
public class FixSysDateUsingPowerMock2 {

    OutputDate output = new OutputDate();

    @Before
    public void setUp() {
        setDatemock();
    }

    @Test
    public void test() {
        output.getDate();
    }

    /**
     * システム日付けを固定する
     * この方法だとミリ秒まで固定する
     */
    private static void setDatemock() {
        String strDate = "2018-01-01 10:10:10.111";
        // 時間を2018年1月1日10時10分10秒にセットする(月は0が1月)
        SimpleDateFormat sdFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss.SSS");
        try {
            PowerMockito.whenNew(Date.class).withNoArguments().thenReturn(sdFormat.parse(strDate));
            when(new Date()).thenReturn(sdFormat.parse(strDate));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

固定したい日時を、文字列からDate形に無理やり変換する方法である。
この方法で、ミリ秒も固定することができた。
めでたしめでたし、、、

JMockitで日時固定できないかな?

上記の方法ですっかり安心していたのだが、カバレッジ取得の際に少し困る問題が発生した。
Eclipseでカバレッジを取得する時に使用するツール「Eclemma」がテストランナーでPowerMockRunner.classを指定すると上手く動作しないようなのである。
既知の問題らしく、一応以下の方法のように@Ruleを使用する方法で解決するらしい。

PowerMockを使ってEclemmaでカバレッジも取得する方法はこちら→https://code.i-harness.com/ja/q/1647e8c

しかし、単体試験時の自分はせっかくだし別の方法ないかな~と模索してた。
そしてJMockitライブラリを使用して下記の方法を思いついた。

@RunWith(JMockit.class)
public class FixSysDateUsingJMockit {

    //private staticをつけないとダメ
    @Mocked
    private static OutputDate output = new OutputDate();

    @Before
    public void setUp() throws Exception{
        setDatemock();
    }

    @Test
    public void test() {
        output.getDate();
    }

    private  void setDatemock() throws Exception {
        String strDate = "2018-01-01 10:10:10.111";
        SimpleDateFormat sdFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss.SSS");
        new CurrentTimeMock(sdFormat.parse(strDate));
    }

    /**
     * dateモック用クラス
     */
    public static class CurrentTimeMock extends MockUp<System>{
        Date mockTime;

        public CurrentTimeMock(Date mockTime){
            this.mockTime = mockTime;
        }

        @Mock
        public long currentTimeMillis(){
            return mockTime.getTime();
        }
    }

これで、日付けも固定できてカバレッジも取得できてよかったよかった!