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

blog1.mammb.com

に引き続き、Unitils でのモックオブジェクトの使い方について見ていきます。

Unitilsによるモックサポート

Unitils では、以前は EasyMock によりモックオブジェクトをサポートしていましたが、バージョン 2 から独自でモックオブジェクトをサポートするようになりました(EasyMock を利用することもできます)。
ここでは、Unitils によるモックオブジェクトのサポートについて見ていきます。

プロジェクトの準備

前回までと同様 Maven にてプロジェクト作成します。以下のように指定し、その他はデフォルトを選択します。

> mvn archetype:generate

Define value for groupId: : etc9
Define value for artifactId: : unitils-mock


作成された pom.xml を以下のように編集します。なお、EasyMock のサポートには unitils-easymock を指定します。

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>etc9</groupId>
  <artifactId>unitils-mock</artifactId>
  <packaging>jar</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>unitils-mock</name>
  <url>http://maven.apache.org</url>
  <dependencies>
    <dependency>
      <groupId>org.unitils</groupId>
      <artifactId>unitils-mock</artifactId>
      <version>3.0</version>
    </dependency>
    <dependency>
      <groupId>org.unitils</groupId>
      <artifactId>unitils-inject</artifactId>
      <version>3.0</version>
    </dependency>
  </dependencies>
</project>


eclipse のプロジェクトに変換します。

> cd unitils-mock
> mvn eclipse:eclipse


依存関係は以下のようになっています

  o org.unitils:unitils-inject:3.0
  o org.unitils:unitils-core:3.0
  o junit:junit:4.4
  o commons-logging:commons-logging:1.1
  o commons-lang:commons-lang:2.3
  o commons-collections:commons-collections:3.2
  o ognl:ognl:2.6.9
  o org.unitils:unitils-mock:3.0
  o cglib:cglib:2.2
  o asm:asm:3.1
  o asm:asm-analysis:3.1
  o asm:asm-tree:3.1
  o org.objenesis:objenesis:1.1

テスト対象ソースの作成

テスト対象のソースを作成していきます。Item クラスを以下のように作成します。

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

    public Item() { }
    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; }
}


Item を扱う ItemDao インターフェースを作成します。ここでは実際の Dao の実装は作成しません。モックオブジェクトにより作成することになります。

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


Dao インターフェースを利用するサービスを以下のように作成します。

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);
    }
}

テストケースの例

Mockオブジェクトを使ったテストケースが以下のようになります。

@RunWith(UnitilsJUnit4TestClassRunner.class)
public class ItemServiceTest {

    private ItemService itemService;
    private Mock<ItemDao> mockItemDao;
    
    @Before
    public void before() {
        itemService = new ItemService();
        itemService.setItemDao(mockItemDao.getMock());//モック設定
    }
    
    @Test
    public final void testAddNewItem() {
        itemService.addNewItem(1L, "name");
        // save メソッドが呼ばれたかを検証
        mockItemDao.assertInvoked().save(new Item(1L, "name"));
    }

    @Test
    public final void testGetItem() {
        Item item1 = new Item(1L, "name1");
        // get メソッドの戻り値を定義
        mockItemDao.returns(item1).get(1L);
        
        Item item = itemService.getItem(1L);
        assertThat(item.getName(), is("name1"));
    }
}

モックオブジェクトのインスタンス化

モックオブジェクトはコントロールオブジェクトを内包しています。モックのインスタンスを得るには、単に、MockクラスのgetMock()を呼び出すだけです。

Mock<ItemDao> mockItemDao;
ItemDao = mockItemDao.getMock();

モックオブジェクトの振る舞いの定義

モックオブジェクトの振る舞いを定義するには以下のようにします。

mockItemDao.returns(item1).get(1L);

ItemDao インターフェースのgetメソッドを、引数 1L で呼び出した結果、item1 を返却するよう振る舞いの定義をしています。


例外を投げる振る舞いを定義するにはraisesメソッドが用意されています。

mockItemDao.raises(new HogeException()).get(0L);

また、この場合はインスタンスを生成せずに、以下のようにすることもできます。

mockItemDao.raises(HogeException.class).get(0L);


さらに、振る舞いの定義を以下のように独自に定義することもできます。

mockItemDao.performs(new MockBehavior() {
    public Object execute(ProxyInvocation mockInvocation) {
        // Item生成のロジックなど
        return item;
    }
});


モックオブジェクトの呼出が複数行われる場合には、onceReturns、onceRaises または oncePerforms を使うことができます。これらのメソッドは、モックオブジェクトの各呼出に1回だけマッチし、その後はマッチすることがありません。以下のように使用することができます。

    @Test
    public final void testGetItem2() {
        Item item1 = new Item(1L, "name1");
        Item item2 = new Item(2L, "name2");
        mockItemDao.onceReturns(item1).get(1L);
        mockItemDao.onceReturns(item2).get(2L);
	
        Item item = itemService.getItem(1L);
        assertThat(item.getName(), is("name1"));

        item = itemService.getItem(2L);
        assertThat(item.getName(), is("name2"));
    }