Java アドベントカレンダー 2017 5日目の記事です。
以前書いた記事のJMockit のバージョンが古くていつか更新したいなぁと思っていたので更新しました!
この記事は、JUnit実践入門のMockitoについて説明してる部分をJMockitでやってみました、という内容です。
JMockitはJUnitなどでテストを書くときに利用できるモックライブラリです。今回使ったJMockitのバージョンは1.37、JUnitのバージョンは4.12です。

JUnit実践入門 ~体系的に学ぶユニットテストの技法 (WEB+DB PRESS plus)
- 作者: 渡辺修司
- 出版社/メーカー: 技術評論社
- 発売日: 2012/11/21
- メディア: 単行本(ソフトカバー)
- 購入: 14人 クリック: 273回
- この商品を含むブログ (69件) を見る
準備
build.gradleに書きます。
testCompile 'org.jmockit:jmockit:1.37' testCompile 'junit:junit:4.12'
書く順番に要注意なんてことが書いてあるので気をつけましょう・・・。
http://jmockit.org/gettingStarted.html#library
あとは、JUnitのテストクラスに @RunWith(JMockit.class)
をつけます。
公式のチュートリアルはこちら。
http://jmockit.org/tutorial.html
スタブメソッドの定義
早速、以前のバージョンと違いがあります。 NonStrictExpectations
がなくなったようです。代わりっぽいのがないので Expectations
を使います。
@Mocked List<String> stub; @Test public void スタブメソッドの定義() { new Expectations() {{ stub.get(0); result = "Hello"; // スタブメソッドの定義 }}; assertEquals(stub.get(0), "Hello"); }
例外を送出するスタブメソッド
以前は NonStrictExpectations
を使ったサンプルコードを書いてました。 NonStrictExpectations
の場合は呼び出し回数まで厳密に見ないのですが、 Expectations
は定義したスタブ実装が呼び出されないとテストが失敗してしまいます。そのため、以下のテストは失敗します。
@Test(expected = IndexOutOfBoundsException.class) public void 例外を送出するスタブメソッド() throws Exception { new Expectations() {{ // NonStrictExpectationsが使えなくなった // Expectationsは定義した内容が呼ばれないとテストが失敗してしまう stub.get(0); result = "Hello"; stub.get(1); result = "World"; stub.get(2); result = new IndexOutOfBoundsException(); }}; stub.get(2); }
失敗した時のメッセージは以下のようになります。
Caused by: Missing 1 invocation to: java.util.List#get(0) on mock instance: $Impl_List@5a39699c instead got: java.util.List#get(2) at SampleTest$2.<init>(SampleTest.java:38) at SampleTest.例外を送出するスタブメソッド(SampleTest.java:33) Caused by: Missing invocations at SampleTest$2.<init>(SampleTest.java:36) at SampleTest.例外を送出するスタブメソッド(SampleTest.java:33) Caused by: java.lang.IndexOutOfBoundsException at SampleTest.例外を送出するスタブメソッド(SampleTest.java:40)
以下のように、呼び出さないスタブ実装を消してあげればテストが通ります。
@Test(expected = IndexOutOfBoundsException.class) public void 例外を送出するスタブメソッド() throws Exception { new Expectations() {{ // NonStrictExpectationsが使えなくなった // Expectationsは定義した内容が呼ばれないとテストが失敗してしまう // stub.get(0); result = "Hello"; // stub.get(1); result = "World"; stub.get(2); result = new IndexOutOfBoundsException(); }}; stub.get(2); }
void型を返すスタブメソッド
@Test(expected=RuntimeException.class) public void メソッドの戻り値がvoidのスタブメソッド() { new Expectations() {{ stub.clear(); result = new RuntimeException(); }}; stub.clear(); }
任意の引数に対するスタブメソッド
メソッドの引数にスタブオブジェクトを指定する方式に書き換えます。以前は引数に指定する場合は @Mocked
アノテーションがなくても大丈夫だったのですが、
@Test public void 任意の整数に対するスタブメソッド(/* Mockedアノテーションが必要 */ @Mocked final List<String> stub) { new Expectations() {{ stub.get(anyInt); result = "Hello"; }}; assertEquals(stub.get(0), "Hello"); assertEquals(stub.get(1), "Hello"); assertEquals(stub.get(999), "Hello"); }
ちなみに @Mocked
をつけなかった時のメッセージは以下なので、気がつかないとハマりそうです。。
java.lang.Exception: Method 任意の整数に対するスタブメソッド should have no parameters
スタブメソッドの検証
特に変わらず Verifications
は使えました。
@Test public void スタブメソッドの検証(@Mocked final List<String> mock) { mock.clear(); mock.add("Hello"); mock.add("Hello"); new Verifications() {{ mock.clear(); times = 1; mock.add("Hello"); times = 2; mock.add("World"); times = 0; }}; }
Exceptations
を使う方法も変わらず。
@Test public void スタブメソッドの検証_Exceptationsで指定(@Mocked final List<String> mock) { new Expectations() {{ mock.clear(); times=1; mock.add("Hello"); times=2; mock.add("World"); times=0; }}; mock.clear(); mock.add("Hello"); mock.add("Hello"); }
部分的なモックオブジェクト
これがうまくいきませんでした! ArrayList
の size()
だけ書き換えたい場合です。
以下のページを読むとこれで良さそうなのですが、 StackOverflowError
が起きてしまいました。原因が分からず・・・。
@Test public void 部分的なモックオブジェクト() { new MockUp<ArrayList<String>>() { @Mock public int size() { return 100; } }; final List<String> mock = new ArrayList<>(); mock.add("Hello"); assertEquals(mock.get(0), "Hello"); assertEquals(mock.size(), 100); }
2017/12/13 追記
ちなみに、ArrayListではなく、例えばStackを使った場合はうまくいきました。。
@Test public void 部分的なモックオブジェクト() { new MockUp<Stack<String>>() { @Mock public int size() { return 100; } }; final Stack<String> mock = new Stack<>(); mock.push("Hello"); assertEquals(mock.firstElement, "Hello"); assertEquals(mock.size(), 100);
スパイオブジェクト
こちらは以前から特に変わらず。
private static class SpyExample { Logger logger = Logger.getLogger(this.getClass().getName()); public void doSomething() { logger.info("doSomething"); } } @Test public void JMockitのMockUpAPIを使ったテスト() { // Setup SpyExample sut = new SpyExample(); final StringBuilder infoLog = new StringBuilder(); new MockUp<Logger>() { @Mock public void info(Invocation invocation, String message) { infoLog.append(message); invocation.proceed(); } }; sut.logger = Logger.getLogger(this.getClass().getName()); // Exercise sut.doSomething(); // Verify assertEquals(infoLog.toString(), "doSomething"); }
おしまい
うーむ、部分的に書き換える方法がうまくいかないのは悔しいですね。また時間を見つけて調べてみたいと思います!