Using Mockito, how do I match against the key-value pair of a map?

后端 未结 4 1151
清酒与你
清酒与你 2021-02-12 11:50

I need to send a specific value from a mock object based on a specific key value.

From the concrete class:

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


        
相关标签:
4条回答
  • 2021-02-12 12:09

    Doesn't this work?

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

    The Map parameter should behave the same way as other parameters.

    0 讨论(0)
  • 2021-02-12 12:14

    Seems like what you need is an 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;
            }
        }
    });
    

    But what seems like a better idea is to not use primitive Map as parameter to your search method - you could probably transform this map into a pojo with price and productName attributes. Just an idea :)

    0 讨论(0)
  • 2021-02-12 12:27

    If you just want to "match" against a particular Map, you can use some of the answers above, or a custom "matcher" that extends Map<X, Y>, or an ArgumentCaptor, like this:

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

    See also more answers here: Verify object attribute value with mockito

    0 讨论(0)
  • 2021-02-12 12:31

    I found this trying to solve a similar issue creating a Mockito stub with a Map parameter. I didn't want to write a custom matcher for the Map in question and then I found a more elegant solution: use the additional matchers in hamcrest-library with mockito's argThat:

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

    If you need to check against multiple entries then you can use other hamcrest goodies:

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

    This starts to get long with non-trivial maps, so I ended up extracting methods to collect the entry matchers and stuck them in our 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()]);
    }
    

    So I'm left with:

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

    There's some ugliness associated with the generics and I'm suppressing one warning, but at least it's DRY and hidden away in the TestUtil.

    One last note, beware the embedded hamcrest issues in JUnit 4.10. With Maven, I recommend importing hamcrest-library first and then JUnit 4.11 (now 4.12) and exclude hamcrest-core from JUnit just for good measure:

    <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>
    

    Edit: Sept 1, 2017 - Per some of the comments, I updated my answer to show my Mockito dependency, my imports in the test util, and a junit that is running green as of today:

    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);
            }
        }
    }
    
    0 讨论(0)
提交回复
热议问题