22

我需要根据特定键值从模拟对象发送特定值。

从具体类:

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

从测试用例:

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

如何模拟此键值对的此方法调用?

4

5 回答 5

26

我发现这试图解决创建带有 Map 参数的 Mockito 存根的类似问题。我不想为有问题的 Map 编写自定义匹配器,然后我找到了一个更优雅的解决方案:将hamcrest-library中的附加匹配器与 mockito 的 argThat 一起使用:

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

如果您需要检查多个条目,那么您可以使用其他 hamcrest 好东西:

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

有一些与泛型相关的丑陋之处,我正在压制一个警告,但至少它是 DRY 并且隐藏在 TestUtil 中。

最后一点,请注意JUnit 4.10 中的嵌入式 hamcrest 问题。使用 Maven,我建议先导入 hamcrest-library,然后再导入 JUnit 4.11(现在是 4.12),并从 JUnit 中排除 hamcrest-core,只是为了更好地衡量:

<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 依赖项、我在测试实用程序中的导入以及截至今天运行绿色的 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);
        }
    }
}
于 2014-03-28T17:30:16.130 回答
17

如果您只想与特定地图“匹配”,您可以使用上面的一些答案,或扩展 Map<X, Y> 或 ArgumentCaptor 的自定义“匹配器”对象,如下所示:

ArgumentCaptor<Map> argumentsCaptured = ArgumentCaptor.forClass(Map.class);
verify(mock, times(1)).method((Map<String, String>) argumentsCaptured.capture());
assert argumentsCaptured.getValue().containsKey("keynameExpected"); 
// argumentsCaptured.getValue() will be the first Map it called it with.
// argumentsCaptured.getAllValues() if it was called more than times(1)

在此处还可以查看更多答案:使用 mockito 验证对象属性值

如果要捕获多个地图:

ArgumentCaptor<Map> argumentsCaptured = ArgumentCaptor.forClass(Map.class);
ArgumentCaptor<Map> argumentsCaptured2 = ArgumentCaptor.forClass(Map.class);
verify(mock, times(1)).method(argumentsCaptured.capture(), argumentsCaptured2.capture());
assert argumentsCaptured.getValue().containsKey("keynameExpected"); 
assert argumentsCaptured2.getValue().containsKey("keynameExpected2"); 
....
于 2014-10-29T20:34:37.417 回答
3

这不行吗?

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

Map参数的行为方式应与其他参数相同。

于 2010-04-05T19:01:13.913 回答
3

似乎您需要的是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数据用作搜索方法的参数 - 您可能可以将此地图转换为带有priceproductName属性的 pojo。只是一个想法:)

于 2011-03-09T05:08:25.693 回答
1

对于像我这样遇到这个问题的人,实际上有一个基于 Lambdas 的非常简单的解决方案:

when(mock.search(argThat(map -> "PRICE".equals(map.get("xpath"))))).thenReturn("$100.00");

解释:argThat期望ArgumentMatcher一个函数接口,因此可以写成 Lambda。

于 2021-12-07T11:34:39.700 回答