MockitoのArgumentMatcherの使い方

http://docs.mockito.googlecode.com/hg/latest/org/mockito/ArgumentMatcher.htmlを見ると、このクラスを使って以下のように、IsListOfTwoElementsクラスのような独自のArgument Matcherを作る事が出来る。

class IsListOfTwoElements extends ArgumentMatcher<List> {
    public boolean matches(Object list) {
        return ((List) list).size() == 2;
    }
}

List mock = mock(List.class);
when(mock.addAll(argThat(new IsListOfTwoElements()))).thenReturn(true);
mock.addAll(Arrays.asList("one", "two"));
verify(mock).addAll(argThat(new IsListOfTwoElements()));


また、argThat(new IsListOfTwoElements())の所は、以下のようにメソッドにすると、可読性が高くなると書かれている。

verify(mock).addAll(argThat(new IsListOfTwoElements()));
//becomes
verify(mock).addAll(listOfTwoElements());


メソッドにするやり方は書かれていないが、以下のようにすれば良い。

class IsListOfTwoElements extends ArgumentMatcher<List> {
    public boolean matches(Object list) {
        return ((List) list).size() == 2;
    }
    public static List listOfTwoElements() {
        return argThat(new IsListOfTwoElements());
    }
}


ちなみにジェネリックを使うと以下のような感じか。

class IsListOfTwoElements extends ArgumentMatcher<List<String>> {
    @Override
    public boolean matches(Object list) {
        return ((List<?>) list).size() == 2;
    }	
    public static List<String> listOfTwoElements() {
        return argThat(new IsListOfTwoElements());
    }
}

@SuppressWarnings("unchecked")
List<String> mock = mock(List.class);
when(mock.addAll(argThat(new IsListOfTwoElements()))).thenReturn(true);
mock.addAll(Arrays.asList("one", "two"));
verify(mock).addAll(listOfTwoElements());

System.out.printとかSystem.exitとかのユニットテストを補助してくれるルールを試してみる

System.out.printlnとかSystem.exitをテストするのはちょっと面倒なのだが、その辺りを補助してくれるJUnitのルールのコレクション(System Rules)があったので試してみた。


【事前準備】
System Rulesを使うにはApache Commons IOが必要という事でダウンロードしておく。


ユニットテストの例】
テスト対象クラスは以下のように標準出力や標準エラー出力に文字列を出力するメソッドやSystem.exit(0)を呼び出すメソッドがある。

public class MyClass {
    public void out() {
        System.out.print("Hoge");
    }	
    public void err() {
        System.err.print("Moge");
    }	
    public void exit() {
        System.exit(0);
    }	
}


上記のテスト対象クラスをSystem Rulesを使ってテストするコードは以下のようになる。かなりシンプルにテストコードが書けた。

public class MyClassTest {	
    @Rule
    public final StandardOutputStreamLog stdout = new StandardOutputStreamLog();	
    @Rule
    public final StandardErrorStreamLog stderr = new StandardErrorStreamLog();	
    @Rule
    public final ExpectedSystemExit exit = ExpectedSystemExit.none();	

    MyClass sut;
    @Before
    public void setUp() throws Exception {
        sut = new MyClass();
    }
    @Test
    public void testOut() {
        sut.out();
        assertThat(stdout.getLog(), is("Hoge"));
    }
    @Test
    public void testErr() {
        sut.err();
        assertThat(stderr.getLog(), is("Moge"));
    }	
    @Test
    public void testExit() {
        exit.expectSystemExitWithStatus(0);
        sut.exit();
    }	
}


System Rulesは上記以外にも、システムプロパティのテストを補助するProvideSystemPropertyやセキュリティーマネージャのテストを補助するProvideSecurityManagerといったルールがあるようだ。


【関連記事】

MockitoのMatchers.anyObject()の使い方

MockitoのMatchers.anyObject()は以下のmethodA()のように、あるオブジェクトを引数に持つメソッドが呼び出されたかどうかを、Mockitoで検証する際等に使うことが出来る。

public class MyClass {
    
    private MyField field;
    	
    public void setField(MyField field) {
        this.field = field;
    }
    
    public void methodA(String text) {
        field.methodB(new MyObject(text));
    }
}


具体的な検証コードは以下。

public class MyClassTest {

    MyField mock;
    MyClass sut;

    @Before
    public void setUp() throws Exception {
        mock = mock(MyField.class);
        sut = new MyClass();
        sut.setField(mock);
    }

    @Test
    public void testMethodA() {
        sut.methodA("test");
        // methodA()を呼び出すとその先でMyFieldのmethodB()が呼び出される事を検証
        verify(mock).methodB((MyObject)anyObject());
    }

}


なお、検証コードを以下のようにしても良さそうなのだが、実際にmethodB()が呼び出された際に渡されたオブジェクトと同一のインスタンスでは無いため、以下はエラーとなってしまう。このような場合にMatchers.anyObject()が使える。

verify(mock).methodB(new MyObject("test"));

Mockitoでprivateなフィールドをモック化する方法

以下のようにprivateなフィールドをモック化する場合、リフレクションを使うと出来るのだが、Mockitoにはそのリフレクションを簡単に使えるWhiteboxというユーティリティクラスがある。

public class MyClass {

    private final MyField field = new MyField();

    public void methodA() {
        field.methodB();
    }

}

Whiteboxクラスの使い方は以下(MyFieldクラスをモック化し、メソッドが呼ばれている事を検証する例)。

public class MyClassTest {

    MyClass sut;
	
    @Before
    public void setUp() throws Exception {
        sut = new MyClass();
        Whitebox.setInternalState(sut, "field", mock(MyField.class));
    }

    @Test
    public void testMethodA() {
        sut.methodA();
        verify((MyField)Whitebox.getInternalState(sut, "field")).methodB();
    }

}

ドットインストールのExpress入門の補足

ドットインストールの「Express入門」「#19 記事を更新/削除してみよう」でちょっと躓いたのでメモしておく。

具体的には記事の削除ボタンを押すと、一番上の記事しか消えないという状況に。
で、理由を調べてみるとpost.jsの以下の記事削除処理で、req.body.idがundefinedになっている事が原因と分かった(splice()の第一引数にundefinedを渡すと0と同じになる?)。

exports.destroy = function(req, res) {
    posts.splice(req.body.id, 1);
    res.redirect('/');
};

で、何でundefinedになってしまうかと言うと、index.ejsで以下のようにidをhidden属性で渡していないから。

<form method="post" action="/posts/<%= i %>">
<input type="hidden" name="_method" value="delete">
<input type="submit" value="del">
</form>

ということで、以下のようにすれば問題解決。

<form method="post" action="/posts/<%= i %>">
<input type="hidden" name="id" value="<%= i %>">
<input type="hidden" name="_method" value="delete">
<input type="submit" value="del">
</form>

ドットインストールのNode.js入門の補足

ドットインストールの「Node.js入門」というレッスンでは、「#14 MongoDBに接続してみよう」というのがあるが、これを実施するためには事前にMongoDBのインストールが必要である(当たり前ではあるが)。

レッスンだと以下のようなコマンドを叩いているが、これはあくまでNode.js用のMongoDBドライバ(node-mongodb-native)をインストールしているだけである。

$ npm install mongodb

MongoDB自体のインストールとセットアップは、ドットインストールの「さくらのVPS入門」「#21 MongoDBを導入しよう」というのがあって、これを見れば良い。

奥さんの携帯を機種変更した際のメモ(ソフトバンク)

今月奥さんの携帯を機種変更した際の事をメモしておく。

Wホワイトの解約で少し手間取った。
店頭での説明だとカスタマーサポート【157】に電話すればすぐに解約出来るとの事だったが、自動音声のガイドだとWホワイトには未加入ということで、解約出来なかった。
そこで、157 → 4(各種変更手続き) → 9(その他、ご契約内容に関するお問い合わせ)で、カスタマーサポートの人に聞いてみると、Wホワイトへの変更申請中というステータスとのこと(一ヶ月単位での切り替えのためそうなっているだとか)。
ということで、その場で解約を依頼すると、変更申請のキャンセルという形ですぐに対応してもらえた。