JUnit実践入門のMockitoの部分をJMockitでやってみた

下書きの状態で眠ってた記事を書きました。

タイトルの通りで、JUnit実践入門のMockitoについて説明してる部分をJMockitでやってみました。
すごく勉強になる本でした。読むと色々な知識がつながってすっきりです。

JMockitは、Mockitoと比べていいところは、staticメソッドだろうがコンストラクタだろうがfinalなクラスのメソッドだろうがモックオブジェクトで振る舞いを制御できちゃいます。
よくないところは、ソースがMockitoを使った場合の方が見やすいとこですかね。個人的にですが。

試したJMockitのバージョンは0.999.18です。

JMockitのチュートリアルが詳しく書かれているので、これを読むと色々分かるかもです。

The JMockit Testing Toolkit Tutorial

スタブメソッドの定義

JMockitには大きく分けて3つのAPIの分けられます。

  • Expectations API
  • Verifications API
  • Mockups API

Expectations APIはスタブメソッドを定義するために、Verifications APIはスタブメソッドがどのように呼び出されたかを検証するために、Mockups APIはspyオブジェクトを生成するために、それぞれ使用することができます。

JMockitでは、メンバー変数に@Mockedアノテーションを付加してスタブオブジェクトを宣言できます。
下の例で@Mockedアノテーションを使っています。
そして、NonStrictExpectationsクラスをnewしてインスタンス初期化子で、スタブメソッドの定義を行います。
result = "Hello" というのがスタブメソッドの返り値を指定しているところです。resultという変数に代入した値が、直前のメソッド呼び出しの返り値になります。1行で書いてますが別に2行になっても構いません。
下の例だと、stub.get(0)という呼び出しで、"Hello"が返ってくるってことですね。

@Mocked
List<String> stub; // スタブオブジェクトを宣言

@Test
public void スタブメソッドの定義() {
    new NonStrictExpectations() {{
        stub.get(0); result = "Hello"; // スタブメソッドの定義
    }};
    assertThat(stub.get(0), is("Hello")); // 検証
}

例外を送出するスタブメソッド

例外を送出させる場合は、resultに例外オブジェクトを代入します。

@Mocked
List<String> stub;

@Test(expected=IndexOutOfBoundsException.class)
public void 例外を送出するスタブメソッド() {
    new NonStrictExpectations() {{
        stub.get(0); result = "Hello";
        stub.get(1); result = "World";
        stub.get(2); result =  new IndexOutOfBoundsException();
    }};
    stub.get(2); // 例外が送出される
}

void型を返すスタブメソッド

voidの場合も変わらすresultに代入すれば例外が発生します。

@Mocked
List<String> stub;

@Test(expected=RuntimeException.class)
public void メソッドの戻り値がvoidのスタブメソッド() {
    new NonStrictExpectations() {{
        stub.clear(); result = new RuntimeException();
    }};
    stub.clear();
}

任意の引数に対するスタブメソッド

任意の引数としたい場合は、anyIntという変数を指定します。Mockitoと似てますね。
また、@Mockedアノテーションを付加したメンバー変数を消しました。代わりにテストメソッドの引数でスタブオブジェクトを指定しています。
JMockitでは、メソッドの引数にスタブオブジェクトを指定する方式も可能です。

@Test
public void 任意の整数に対するスタブメソッド(final List<String> stub) {
    new NonStrictExpectations() {{
        stub.get(anyInt); result = "Hello";
    }};
    assertThat(stub.get(0), is("Hello"));
    assertThat(stub.get(1), is("Hello"));
    assertThat(stub.get(999), is("Hello"));
}

スタブメソッドの検証

さて、スタブメソッドが引数"Hello"で2回呼び出されて、引数"World"では1回も呼び出されてない、ということを検証したい場合ですが、JMockitではVerifications APIを使います。
下のように、Verificationsクラスをnewしてインスタンス初期化子に検証処理を書きます。
使ったことないですが、MockitoでいうところのatMostメソッドと同じことができそうな、maxTimesとかもチュートリアルを見るとあります。

@Test
public void スタブメソッドの検証(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;
    }};
}

また、Verifications APIを使わずに、Expectations APIのみでも同じ上と同じ検証ができます。
この場合、NonStrictExpectationsクラスではなく、Expectationsクラスを使ってスタブメソッドを定義します。

@Test
public void スタブメソッドの検証_Exceptationsで指定(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");
}

部分的なモックオブジェクト

JMockitでもMockitoと同じように実オブジェクトを利用し、部分的なモックオブジェクトを作ることが可能です。
実オブジェクトを、Expectationsのコンストラクタ引数に指定します。

@Test
public void 部分的なモックオブジェクト() {
    final List<String> mock = new ArrayList<String>();
    new Expectations(mock) {{
        mock.size(); result = 100;
    }};
    mock.add("Hello");
    assertThat(mock.get(0), is("Hello"));
    assertThat(mock.size(), is(100));
}

スパイオブジェクト

JMockitでスパイオブジェクトを作成するには、Mockups APIを使用します。
MockUpクラスをnewする時に型パラメータとして監視対象のクラスを指定します。そして、インスタンス初期化子で監視対象となるメソッドを宣言します。
メソッドの宣言は、監視対象のメソッドと同じ名前、同じ返り値の型、引数は第1引数にInvocation、第2引数以降は監視対象メソッドの引数、というようにします。
実オブジェクトでの本来の振る舞いをさせるには、invocationのproceedメソッドを呼び出します。

private static class SpyExample {
    Logger logger = LoggerFactory.getLogger(JMockitMockingTest.class);
    public void doSomething() {
        logger.info("doSomething");
    }
}

@Test
public void JMockitのMockUpAPIを使ったテスト() {
    // Setup
    SpyExample sut = new SpyExample();
    final StringBuilder infoLog = new StringBuilder();
    MockUp<Logger> spy = new MockUp<Logger>() {
        @Mock
        public void info(Invocation invocation, String message) {
            infoLog.append(message);
            invocation.proceed();
        }
    };
    sut.logger = spy.getMockInstance();
    // Exercise
    sut.doSomething();

    // Verify
    assertThat(infoLog.toString(), is("doSomething"));
}


こんな感じでできました。
やっぱりMockitoの方がソース見やすいですね。

ただ、saticメソッドやコンストラクタの振舞いも制御できるので、レガシーアプリと戦う時とかに使えそうです。

JUnit実践入門 ~体系的に学ぶユニットテストの技法 (WEB+DB PRESS plus)

JUnit実践入門 ~体系的に学ぶユニットテストの技法 (WEB+DB PRESS plus)