mockitoとJMockitについてのメモ

mockitoとJMockitについて簡単にまとめました。両者ともJavaのユニットテスト向けモックフレームワークなのですが、使い方は全然違います。さわりをちょっと調べただけです。有識者のコメント歓迎します。

2022-02-25 更新：JMockitは、活動を停止しているようです。

# 対比

|            | mockito              |  JMockit            | 
|-----------:|:--------------------:|:-------------------:|
| URL        | https://site.mockito.org/  | http://jmockit.github.io/ |    
| パッケージ名| org.mockito.*        | mockit.*            |    
| API        | https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html       |   見当たらず |  

プロダクト名、パッケージ名ともに紛らわしいので検索するときは注意。

# コードサンプル

 `Logic.java`が`Calc.java`を呼んでいて、CalcをモックしてLogicの動作を確認したいとします。


## mockitoでのサンプル

``` LogicTestInMockito.java
package org.example;

import org.junit.Before;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.when;

public class LogicTestInMockito {

  @Mock
  Calc calc;

  @InjectMocks
  Logic logic;

  @Before
  public void before() {
    MockitoAnnotations.initMocks(this);
  }

  @Test
  public void testAsUsual() throws Exception {
    logic.calc = new Calc();
    int[] result = logic.getDivisibleNumbers();
    assertTrue(result[0] % result[1] == 0);
  }

  @Test
  public void testInMockito() throws Exception {
    when(calc.remainder(anyInt(), anyInt())).thenAnswer(i -> {
      // 公式ドキュメントには書いてありませんが、ここはラムダが使えます。
      return ((Integer) i.getArguments()[1]) == 5 ? 0 : 1;
    });
    int[] result = logic.getDivisibleNumbers();
    assertTrue(result[1] == 5);
  }
}
```

## JMockitでのサンプル　（Expectations APIでの例）

``` LogicTestInJMockit.java
package org.example;

import mockit.Expectations;
import mockit.Mocked;
import mockit.Verifications;
import mockit.integration.junit4.JMockit;
import org.junit.Test;
import org.junit.runner.RunWith;

import static org.junit.Assert.assertTrue;

@RunWith(JMockit.class)
public class LogicTestInJMockit {

  @Test
  public void testAsUsual() throws Exception {
    Logic logic = new Logic(new Calc());
    int[] result = logic.getDivisibleNumbers();
    assertTrue(result[0] % result[1] == 0);
  }


  @Test
  public void testInJMockit(@Mocked Calc calc) throws Exception {
    new Expectations() {{
      calc.remainder(anyInt, 5);
      result = 0;
      calc.remainder(anyInt, anyInt);
      result = 1;
    }};
    Logic logic = new Logic(calc);
    int[] result = logic.getDivisibleNumbers();
    new Verifications() {{
      calc.remainder(anyInt, 5);
      minTimes = 1;
    }};
    assertTrue(result[1] == 5);
  }
}
```

## JMockitでのサンプル　（Mockups APIでの例）


``` LogicTestInJMockitMockups.java
package org.example;

import mockit.Mock;
import mockit.MockUp;
import org.junit.Test;

import static org.junit.Assert.assertTrue;

public class LogicTestInJMockitMockups {

  // このテストが、testInJMockitWithMockups() の後に実行されると
  // モックしていないCalcを使っているつもりがモックされたものになってしまう様子。
  @Test
  public void testAsUsual() throws Exception {
    Logic logic = new Logic(new Calc());
    int[] result = logic.getDivisibleNumbers();
    assertTrue(result[0] % result[1] == 0);
  }


  @Test
  public void testInJMockitWithMockups() throws Exception {
    new MockUp<Calc>() {
      @Mock()
      public int remainder(int dividend, int divisor) {
        return divisor == 5 ? 0 : 1;
      }
    };
    Logic logic = new Logic(new Calc());
    int[] result = logic.getDivisibleNumbers();
    assertTrue(result[1] == 5);
  }
}
```

## モック対象のコード

``` Calc.java 
package org.example;

public class Calc {
  int remainder(int dividend, int divisor) {
    return dividend % divisor;
  }
}
```

## テスト対象のコード

``` Logic.java
package org.example;

public class Logic {
  Calc calc;

  Logic(Calc calc) {
    this.calc = calc;
  }
  /**割り切れる数字のペアをアトランダムに返す。*/
  int[] getDivisibleNumbers() {
    while (true) {
      int candidate = (int) (Math.random() * 100) + 1;
      int candidate2 = (int) (Math.random() * 10) + 1;
      if (calc.remainder(candidate, candidate2) == 0) {
        return new int[]{candidate, candidate2};
      }
    }
  }
}
```


# 寸評

mockitoはモックの挙動を　`when()` `doReturn()` `thenAnswer()` で決めていくスタイル。 チェーン表記が綺麗とも煩くなるとも言われている様子。素朴ですがたくさんやると限界があるかもしれません。

JMockitはAPIにクセがあるが、慣れればOKという記事をちらほら見つかります。（なにげなくインスタンスイニシャライザ使ってて慣れるのに時間がかかるひとはいそう。）個人的には Mockups APIの場合、モックするメソッドに対してコンパイラのチェックがかからないことや、`new MockUp<Calc>(){...} `した後は、モックしていない`Calc`が使えなくなるなど、学習曲線がちょっと厳しいと思われました。先の例だと`LogicTestInJMockitMockups.java`は、テストメソッドの順序次第で`testAsUsual()`が失敗します。また、mockitoよりも呼び出し回数のテストとか実引数をモックが取得できるとか細かい機能が充実していそうな様子です。(`mockit.Invocation` `mockit.Mock#invocations`など。)

# 私の結論

学習曲線が低そう。ラムダが使えて幸せな気持ちになれそう。ロゴのデザインが良い。などという薄っぺらい理由でとりあえずmockitoを使ってみようと思います。限界を感じたら JMockitも深耕してみようかなと。

# 参考リンク

- http://dontpanic.42.nl/2013/04/mockito-powermock-vs-jmockit.html
- http://stackoverflow.com/a/6439134

見つかった比較記事は両方とも JMockit 押し。ただし2013年とかちょっと古い。多分このネタ自体は既にhotではなくて、両者それなりに使われているのではないかと想像する次第。

# バージョン番号など

- mockito 1.10.19
- JMockit 1.21
- Java 8