jMockの簡単なまとめ

jMockとは

モックの振る舞い定義が、慣れると心地いいモックライブラリです。本家サイトは以下。
http://www.jmock.org/index.html

jMock利用準備

クラスパスへ以下のjarを追加します。

  • jmock-2.5.1.jar
  • hamcrest-core-1.1.jar
  • hamcrest-library-1.1.jar
  • jmock-junit4-2.5.1.jar

今回はJUnit4とcommons-langを使うので以下も追加

  • junit-4.7.jar
  • commons-lang-2.4.jar

テスト対象ソースの作成

Item クラスを以下のように作成します。equals()を実装しておきます。

public class Item {
    private Long id;
    private String name;

    public Item(Long id, String name) {
        this.id = id; this.name = name;
    }

    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }

    public boolean equals(Object obj){
    	return EqualsBuilder.reflectionEquals(this, obj);
    }
}


Itemクラスを操作するItemDaoインターフェースを以下のように作成します。

public interface ItemDao {
    Item get(Long id);
    void save(Item item);
    List<Item> findByName(String name);
}


ItemDaoインターフェースを使用するItemServiceを以下のように作成します。

public class ItemService {
    private ItemDao itemDao;

    public void setItemDao(ItemDao itemDao) {
        this.itemDao = itemDao;
    }

    public void addNewItem(Long id, String name) {
        Item item = new Item(id, name);
        itemDao.save(item);
    }

    public Item getItem(Long id) {
        return itemDao.get(id);
    }

    public List<Item> findByName(String name) {
        return itemDao.findByName(name);
    }
}

テストケースの作成

ItemServiceのテストケースをjMockを使用して作成した例です。

@RunWith(JMock.class)
public class ItemServiceTest {

    Mockery context = new JUnit4Mockery();

    @Test
    public final void testAddNewItem() {
        final ItemDao itemDaoMock = context.mock(ItemDao.class);
        ItemService itemService = new ItemService();
        itemService.setItemDao(itemDaoMock);

        context.checking(new Expectations(){{
            oneOf (itemDaoMock).save(new Item(1L, "Name"));
        }});
        
        // execute
        itemService.addNewItem(1L, "Name");
    }
}

Mockery の mock メソッドに対象のクラスを渡してモックオブジェクトを得ます。

final ItemDao itemDaoMock = context.mock(ItemDao.class);

モックがどのように呼び出されるかを以下の・・・に定義します。

context.checking(new Expectations(){{
    ・・・
}});

{{ }} で匿名クラスのインスタンス初期化子にモックの振る舞いを定義することになります。
以下のようなマップの初期化とかと同じです(商用コードでは以下の書き方はお勧めしません)。

Map<String, String> map = new HashMap<String, String>(){{ put("key1", "value1"); }};


モックの振る舞いとしてsaveメソッドが指定の引数で1回だけ呼び出されることを想定、の定義が以下となります。Itemオブジェクトの同値性が検証されるため、Itemクラスにはequalsを実装しておく必要があります。

oneOf (itemDaoMock).save(new Item(1L, "Name"));

戻り値のある場合

次に戻り値のある ItemDao の get メソッドについて見ていきます。テストメソッドは以下のようになります。

@Test
public final void testGetItem() {
    final ItemDao itemDaoMock = context.mock(ItemDao.class);
    ItemService itemService = new ItemService();
    itemService.setItemDao(itemDaoMock);

    context.checking(new Expectations(){{
        oneOf (itemDaoMock).get(1L); will(returnValue(new Item(1L, "Name")));
    }});
    
    assertThat(itemService.getItem(1L), is(new Item(1L, "Name")));
}

will(returnValue()) によって戻り値を定義します。

呼び出し回数

前述までは、以下のようにoneOfにてモックの該当メソッドが1回だけ呼び出される検証を定義しました。

oneOf (itemDaoMock).get(1L);

呼び出し回数の定義はoneOf意外にも以下のもの準備されています。

メソッド 説明
oneOf 1回だけ呼び出される
exactly(n).of n 回呼び出される(1回の場合はexactly(1)と書ける)
atLeast(n).of 最低 n 回呼び出される
atMost(n).of 最大 n 回呼び出される
between(min, max).of min 〜 max 回呼び出される
allowing 何度呼び出されても許容(0回も許容)
ignoring allowingと同様。無視する場合は明示的にこちらを使用する
never 1回も呼び出されない

例えば、getメソッドが少なくとも1回呼び出されることを検証するには以下のようにします。

context.checking(new Expectations(){{
    atLeast(1).of (itemDaoMock).get(1L);
}});

モックに与えられる引数の検証

先のgetメソッドに与えられる引数として、何らかのLong値のように、ゆるい検証とすることができます。

context.checking(new Expectations(){{
    oneOf (itemDaoMock).get(with(any(Long.class)));
}});

with とともに Matchers を指定することで、上記例ではLong型の引数が与えられることを検証します。代表的な Matchers として以下があります。

メソッド 説明
equal(n) 引数が n と同値であることを検証
same(o) 引数が o と同一であることを検証
any(Class type) 引数がtypeと同じ型であることを検証
a(Class type) an(Class type) 引数がtypeのインスタンスか、そのサブクラスであることを検証
aNull(Class type) 引数が null であることを検証
aNonNull(Class type) 引数が null でないことを検証
not(m) 引数が Matcher m でないことを検証(Matcher は別途指定)
anyOf(m1, m2, ..., mn) 引数が与えられた Matchers のどれかに合致することを検証
allOf(m1, m2, ..., mn) 引数が与えられた Matchers の全てに合致することを検証

Matchers は Hamcrest Matchers のクラスを使用することができます。

モックの戻り値の定義

先の例では戻り値の定義として以下を指定しました。

oneOf (itemDaoMock).get(1L); will(returnValue(new Item(1L, "Name")));

戻り値の定義は以下のもの準備されています。

メソッド 説明
will(returnValue(v)) v を返却
will(returnIterator(c)) コレクションcのイテレータを返却
will(returnIterator(v1, v2, ..., vn)) 各呼び出しで v1 から vn までの要素を返すイテレータを返却
will(throwException(e)) 例外eを投げる
will(doAll(a1, a2, ..., an)) 各呼び出しで a1 から an の全てのアクションを実行する