(注:このブログはもう更新していません)この日記は私的なものであり所属会社の見解とは無関係です。 GitHub: takahashikzn

[クラウド帳票エンジンDocurain]

Mockitoの真価はspyにあり


以前の日記でEasyMockをご紹介したのですが、現在の現場ではMockitoを使っています。

それにしてもモックキット?モキット?モキート?どう読むんだろう…
ロゴは有名なカクテル「モヒート」ではあるが。僕はミントが苦手なので余り飲まないけど。


Mockitoは比較的新しいモックユーティリティです。
何か革新的な機能を備えているかというとそうでもないのですが、使い勝手が非常に良いのが特徴。
サンプルコードはこんな感じ。

public class VeryHugeBusinessLogic {

    void doSomethingHardProcessing() { ... }

}


public class VeryHugeService {

    @Autowired
    VeryHugeBusinessLogic logic;

    public void execute() {
        logic.doSomethingHardProcessing();
    }

}



/**
 * VeryHugeServiceのテスト
 */
public class VeryHugeServiceTest {

    public void testExecute() {
    
        VeryHugeBusinessLogic mockLogic = Mockito.mock(VeryHugeBusinessLogic.class);

        VeryHugeService service = new VeryHugeService();
        service.logic = mockLogic;

        service.execute();

        // doSomethingHardProcessingが呼ばれたことを確認
        Mockito.verify(mockLogic).doSomethingHardProcessing();
    }

}

spy最高!

さて、ここまでの説明を読んで「EasyMockと大して変わんなんくね?」と思ったそこの貴方。


違うんです。Mockitoには1つだけ、EasyMockにはマネできない素晴らしい機能が存在するのです。
それは何かと言うと...Mockito#spyメソッド。

ほとんどこれだけのために、僕はMockitoへ乗り換える決心をしました。


例えば、次のようなクラスを考えてみます。

import java.sql.*;

public class FooBusinessLogic {

    public void execute() {

        if (isAnyCondition()) {
            doSomething();
        } else {
            doOtherthing();
        }
    }

    protected boolean isAnyCondition() {
        // DBに接続しに行く複雑な処理
    }

    protected void doSomething() {
        // やはりDBに接続しに行く複雑な処理
    }

    protected void doOtherthing() {
        // こいつもDBに接続しに行く複雑な処理
    }
}


こんなクラスがあったとき、従来ならばJUnit

import org.junit.*;

public class FooBusinessLogicTest {

    /**
     * isAnyConditionがtrueを返し、doSomethingが呼ばれることを検証する。
     */
    @Test
    public void testExecuteDoSomethingInvoked() {

        // doSomethingが実行されたか否かの判定フラグ
        final boolean[] doSomethingInvoked = { false };

        FooBusinessLogic logic = new FooBusinessLogic() {
            @Override
            protected boolean isAnyCondition() {
                return true;
            }
            @Override
            protected void doSomething() {
                doSomethingInvoked[0] = true;
            }
            @Override
            protected void doOtherthing() {
                Assert.fail();
            }
        };

        logic.execute();

        // doSomethingが呼ばれたことを確認
        Assert.assertTrue(doSomethingInvoked[0]);
    }
}


とかやっていたわけです。
ホラでた〜!! Javaのダメダメランキング一位の無名クラス。
あと、doSomethingInvoked[0]のあたりも美しくない。


こんな見苦しいコードが、Mockkitoを使うとこんなキレイに変身するわけです。

import static org.mockito.Mockito.*;

public class FooBusinessLogicTest {

    /**
     * isAnyConditionがtrueを返し、doSomethingが呼ばれることを検証する。
     */
    @Test
    public void testExecuteDoSomethingInvoked() {

        FooBusinessLogic logic = spy(new FooBusinessLogic());

        // isAnyConditionの中身を実行せず、強制的にtrueを返す
        doReturn(true).when(logic).isAnyCondition();
        
        // doSomethingが呼ばれても何もしない
        doNothing().when(logic).doSomething();

        // doOtherthingが呼ばれたらアサーションエラー
        doThrow(new org.junit.AssertionFailedError()).when(logic).doOtherthing();

        logic.execute();

        // doSomethingが一度だけ呼ばれたことを確認
        verify(logic, times(1)).doSomething();
    }
}


Mockito#spyのスゴイところは、具象クラスのメソッドのうち、
挙動を入れ替えたいメソッドだけを入れ替えられるということ。

例えばEasyMockだと、

    FooBusinessLogic mockLogic = EasyMock.createMock(FooBusinessLogic.class);

    // isAnyConditionがtrueを返すように仕向ける
    EasyMock.expect(mockLogic.isAnyCondition()).andReturn(true);
    // doSomethingが呼び出されても何もしないように仕向ける
    mockLogic.doSomething();
    EasyMock.expectLastCall();

    // テスト実行…?
    mockLogic.execute();

    EasyMock.replay();

などと書けば行けそうに見えますが、これはダメです。

なぜならmockLogicは完全に元の振る舞いを失っており、中身は空っぽだから。
mockLogic.executeを呼び出してもEasyMockのエラーになって終わるだけなのです。

今日から君もMockito信者

さあ乗り換えるんだ今すぐに。NOW!

追記

PowerMockというのを併用すれば、EasyMockでspy相当のことが出来るようです。

しかし。。。

public static <T> T createPartialMock(Class<T> type,
                                      Class<? super T> where,
                                      String... methodNames)

ん〜?メソッド名を文字列で渡す必要があるのか?
これじゃEclipseリファクタリングしたときに、自動的には追従できないじゃん。


というわけでイマイチな感じです。