Java
JUnit
jMockit
Mockito
PowerMock

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();
}
}

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