web-dev-qa-db-ja.com

Mockitoを使用して、マップのキーと値のペアを照合するにはどうすればよいですか?

特定のキー値に基づいて、モックオブジェクトから特定の値を送信する必要があります。

具体的なクラスから:

map.put("xpath", "PRICE");
search(map);

テストケースから:

IOurXMLDocument mock = mock(IOurXMLDocument.class);
when(mock.search(.....need help here).thenReturn("$100.00");

このキーと値のペアのこのメソッド呼び出しをモックするにはどうすればよいですか?

15
Sean

これは、Mapパラメーターを使用してMockitoスタブを作成する同様の問題を解決しようとしていることがわかりました。問題のマップのカスタムマッチャーを作成したくなかったので、よりエレガントな解決策を見つけました。 hamcrest-library の追加のマッチャーをmockitoのargThatで使用します。

when(mock.search(argThat(hasEntry("xpath", "PRICE"))).thenReturn("$100.00");

複数のエントリをチェックする必要がある場合は、他のハムクレストグッズを使用できます。

when(mock.search(argThat(allOf(hasEntry("xpath", "PRICE"), hasEntry("otherKey", "otherValue")))).thenReturn("$100.00");

これは重要なマップで長くなり始めたので、エントリマッチャーを収集するためのメソッドを抽出し、それらをTestUtilsに貼り付けました。

import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.anyOf;
import static org.hamcrest.Matchers.hasEntry;

import Java.util.ArrayList;
import Java.util.List;
import Java.util.Map;

import org.hamcrest.Matcher;
---------------------------------
public static <K, V> Matcher<Map<K, V>> matchesEntriesIn(Map<K, V> map) {
    return allOf(buildMatcherArray(map));
}

public static <K, V> Matcher<Map<K, V>> matchesAnyEntryIn(Map<K, V> map) {
    return anyOf(buildMatcherArray(map));
}

@SuppressWarnings("unchecked")
private static <K, V> Matcher<Map<? extends K, ? extends V>>[] buildMatcherArray(Map<K, V> map) {
    List<Matcher<Map<? extends K, ? extends V>>> entries = new ArrayList<Matcher<Map<? extends K, ? extends V>>>();
    for (K key : map.keySet()) {
        entries.add(hasEntry(key, map.get(key)));
    }
    return entries.toArray(new Matcher[entries.size()]);
}

だから私は残されています:

when(mock.search(argThat(matchesEntriesIn(map))).thenReturn("$100.00");
when(mock.search(argThat(matchesAnyEntryIn(map))).thenReturn("$100.00");

ジェネリックに関連する醜さがいくつかあり、1つの警告を抑制していますが、少なくともそれはDRYであり、TestUtilに隠されています。

最後に、 JUnit 4.10に埋め込まれたハムクレストの問題 に注意してください。 Mavenでは、最初にhamcrest-libraryをインポートしてからJUnit 4.11(現在は4.12)をインポートし、適切な方法でhamcrest-coreをJUnitから除外することをお勧めします。

<dependency>
    <groupId>org.hamcrest</groupId>
    <artifactId>hamcrest-library</artifactId>
    <version>1.3</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
    <exclusions>
        <exclusion>
            <groupId>org.hamcrest</groupId>
            <artifactId>hamcrest-core</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-all</artifactId>
    <version>1.9.5</version>
    <scope>test</scope>
</dependency>

編集:2017年9月1日-コメントのいくつかに従って、Mockitoの依存関係、テストutilでのインポート、および今日の時点で緑色で実行されているjunitを表示するように回答を更新しました:

import static blah.tool.testutil.TestUtil.matchesAnyEntryIn;
import static blah.tool.testutil.TestUtil.matchesEntriesIn;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.mockito.Matchers.argThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import Java.util.HashMap;
import Java.util.Map;

import org.junit.Test;

public class TestUtilTest {

    @Test
    public void test() {
        Map<Integer, String> expected = new HashMap<Integer, String>();
        expected.put(1, "One");
        expected.put(3, "Three");

        Map<Integer, String> actual = new HashMap<Integer, String>();
        actual.put(1, "One");
        actual.put(2, "Two");

        assertThat(actual, matchesAnyEntryIn(expected));

        expected.remove(3);
        expected.put(2, "Two");
        assertThat(actual, matchesEntriesIn(expected));
    }

    @Test
    public void mockitoTest() {
        SystemUnderTest sut = mock(SystemUnderTest.class);
        Map<Integer, String> expected = new HashMap<Integer, String>();
        expected.put(1, "One");
        expected.put(3, "Three");

        Map<Integer, String> actual = new HashMap<Integer, String>();
        actual.put(1, "One");

        when(sut.search(argThat(matchesAnyEntryIn(expected)))).thenReturn("Response");
        assertThat(sut.search(actual), is("Response"));
    }

    protected class SystemUnderTest {
        // We don't really care what this does
        public String search(Map<Integer, String> map) {
            if (map == null) return null;
            return map.get(0);
        }
    }
}
17
Marquee

特定のマップと「照合」したいだけの場合は、上記の回答のいくつか、Mapを拡張するカスタムの「マッチャー」、または次のようなArgumentCaptorを使用できます。

ArgumentCaptor<Map> argumentsCaptured = ArgumentCaptor.forClass(Map.class);
verify(mock, times(1)).method((Map<String, String>) argumentsCaptured.capture());
assert argumentsCaptured.getValue().containsKey("keyname"); 
// .getValue() will be the Map it called it with.

ここで他の回答も参照してください: mockitoでオブジェクト属性値を確認してください

8
rogerdpack

これは機能しませんか?

Map<String, String> map = new HashMap<String, String>();
map.put("xpath", "PRICE");
when(mock.search(map)).thenReturn("$100.00");

Mapパラメータは、他のパラメータと同じように動作する必要があります。

3
Bozho

必要なのはAnswerのようです:

IOurXMLDocument doc = mock(IOurXMLDocument.class);
when(doc.search(Matchers.<Map<String,String>>any())).thenAnswer(new Answer<String>() {
    @Override
    public String answer(InvocationOnMock invocation) throws Throwable {
        Map<String, String> map = (Map<String, String>) invocation.getArguments()[0];
        String value = map.get("xpath");
        if ("PRICE".equals(value)) {
            return "$100.00";
        } else if ("PRODUCTNAME".equals(value)) {
            return "Candybar";
        } else {
            return null;
        }
    }
});

しかし、より良いアイデアのように思われるのは、検索メソッドのパラメーターとしてプリミティブMapを使用しないことです。おそらくこのマップをprice属性とproductName属性を持つpojoに変換できます。ただのアイデア:)

2
denis.solonenko