単体テストを簡単に Unitils 〜モックオブジェクト 2 〜

前回
blog1.mammb.com

に引き続き、Unitils によるモックオブジェクトのサポートを見ていきます。

メソッド呼び出しの検証

テスト対象のクラスのメソッド呼び出しにより、モックが期待通りに呼び出されたかを検証するには以下のようにします(通常モックオブジェクトのメソッドの戻り値が void の場合に使用)。

@Test
public final void testAddNewItem() {
    itemService.addNewItem(1L, "name");
    mockItemDao.assertInvoked().save(new Item(1L, "name"));
}

このコードでは、itemService のメソッドを呼び出した結果、モックオブジェクトの save メソッドが期待した引数にて呼び出されたかどうかを検証します。assertInvoked()によるメソッド呼び出しの検証は、1回のみ行われることに注意してください。同じメソッド呼び出しが行われる場合には、複数のassertInvoked()を記述する必要があります。


これとは別に、指定した引数でモックのメソッドが呼ばれないことを検証する場合は以下のようにします。

itemService.addNewItem(1L, "name1");
mockItemDao.assertNotInvoked().save(new Item(1L, "name2"));

assertNotInvoked()により、指定したメソッドが指定した引数で呼び出されていないことが検証されます。
以下のように、MockUnitils.assertNoMoreInvocations() を指定すると、テストケースとして明記してあるメソッド以外のメソッド呼び出しが行われた場合には、テスト失敗とすることができます。

itemService.addNewItem(1L, "name1");
mockItemDao.assertInvoked().save(new Item(1L, "name1"));
MockUnitils.assertNoMoreInvocations();

例えば、以下のテストはassertInvoked()の定義が1つしか無いのでテストに失敗することになります。

itemService.addNewItem(1L, "name1");
itemService.addNewItem(2L, "name2");
mockItemDao.assertInvoked().save(new Item(1L, "name1"));
MockUnitils.assertNoMoreInvocations();

MockUnitils.assertNoMoreInvocations() は、テストクラスの @After メソッド内に記載することで、全てのテストメソッドで、モックの呼び出しとして定義したもの以外が呼び出された場合にテストを失敗とすることができます。


assertInvoked()は、メソッドの呼出順序については検証を行いません。メソッドの呼出順序を検証する場合には、以下のように assertInvokedInSequence() を使用します。

itemService.addNewItem(1L, "name1");
itemService.addNewItem(2L, "name2");
mockItemDao.assertInvokedInSequence().save(new Item(1L, "name1"));
mockItemDao.assertInvokedInSequence().save(new Item(2L, "name2"));

これにより、呼び出し順序を考慮した検証が実施されます。

引数マッチャー

Unitils では、モックオブジェクトのテストケースをより便利に記載できる ArgumentMatchers が用意されています。
ArgumentMatchers を利用するために、スタティックインポートを追加し、

import static org.unitils.mock.ArgumentMatchers.*;


以下のようなテストを記述したとします。

List<Item> list = Arrays.asList(new Item(1L, "name1"));
mockItemDao.returns(list).findByName(notNull(String.class));
mockItemDao.returns(null).findByName(isNull(String.class));
	
List<Item> l1 = itemService.findByName("test");
List<Item> l2 = itemService.findByName(null);

これにより、mockItemDaoは、何かしらの非nullなオブジェクトが引数として渡された場合は、listを返却し、引数としてnullが指定された場合にはnullを返却するというモックの振る舞いを定義できます。これにより、多少寛大なモックの定義が可能となります。


ArgumentMatchers には same() というメソッドも用意されており、以下のように記述することで、item1と同じインスタンスでsaveメソッドが呼び出されたかを検証することができます。

mockItemDao.assertInvoked().save(same(item1));