0

How can I match a Map of Object[] in Mockito? I used

  String[] entity= {"entity1"}
  Map<String,Object[]> queryParam = new HashMap<String,Object[]>();
  queryParam.put("entityString",entity);

and

  when(DB.get("SomeString", queryParam)).thenReturn(mockResponse);

But it is not matching. I feel like it is unable to match Object[] and String [] during real call. Please help me.

2
  • 1
    Is there a compelling reason not to just populate the map with whatever values you need and pass that along to the component under test? Commented Apr 13, 2015 at 19:35
  • Yes. It is like a DB call and I am doing unit test. Commented Apr 13, 2015 at 19:41

2 Answers 2

1

Clarified answer (matching a Map)

It is now clear that what you're trying to do is match a Map<String, Object[]> instead of mocking one, which can be tricky: Even though Map supports equals, arrays compare by identity instead of deep equality by default. In this case I would use a few Hamcrest matchers:

private Matcher<Map<String, Object[]>> hasParamArray(
    String key, Object... vals) {
  return hasEntry(
      equalTo(key),
      arrayContaining(vals));
}

// elsewhere
when(DB.get(
        eq("someString"),
        argThat(hasParamArray("entityString", "entity1"))))
    .thenReturn(mockResponse);

As an alternative, write an Answer:

when(DB.get(eq("someString"), anyMap())).thenAnswer(new Answer<Response>() {
  @Override public void answer(InvocationOnMock invocation) {
    Map<String, Object[]> map =
        (Map<String, Object[]>) invocation.getArguments()[1];
    if (/* ... */) {
      return mockResponse1;
    } else if (/* ... */) {
      return mockResponse2;
    } else {
      return defaultResponse;
    }
  }
});

Original answer (mocking a Map)

Note that Object[] and String[] are not compatible types:

Map<String, Object[]> yourMap = new HashMap<>();
String[] yourStringArray = new String[] { "hello" };

// this is a compile error, or at least it should be...
yourMap.put("foo", yourStringArray);

// ...because this would otherwise be a runtime error, which is probably
// what you're experiencing right now.
Object[] yourObjectArray = yourMap.get("foo"); // returns yourStringArray
yourObjectArray[0] = new Object();

return yourStringArray[0].length();  // oops! this is that new Object()!

You can probably switch entity to type Object[] and be done:

// in your class with a call to MockitoAnnotations.initMocks(this) in setUp()
@Mock Map<String, Object[]> queryParam;

when(queryParam.get("someString")).thenReturn(new Object[] { "entity1" });

That said, I would advise doing as David said in the comments, and use a real map instead. The rule of thumb is don't mock data objects, and there are three very good reasons not to mock Map<String, Object[]>:

  1. Mocks are never going to be as good as the real thing. Interfaces can change, as they did to support streams in Java 8, and real implementations will adapt to these changes where mocks will not. This is especially true when mocking real objects, where an addition of a final modifier (for instance) will ruin your mock without actually affecting your system under test.

  2. Unlike a database, a Map is very easy to manually create. Many database systems have many dependencies, and require a lot of memory. Maps have no additional dependencies, and are extraordinarily lightweight and well-tested core components of Java.

    (Even if it were a full database, an in-memory "fake" or some other standalone implementation would be much more robust than any reasonable Mockito mock. Because the JDK provides you with many reasonable implementations, it's an even easier choice.)

  3. To appropriately mock a Map takes an equivalent amount of work, or more. Your test will likely get certain values out of the map, each of which would need to be stubbed with when; a mutable Map can simply accept those with put. Should the Map be mutated by your system, you'd need to anticipate the call and update the mock, where a real Map will have the correct behavior automatically.

    This is to say nothing of the calls containsKey(K), containsValue(V), remove(Object), size(), and the many other methods on Map that you'd need to replace to make a robust test. With a mock, the simplest and most reasonable code changes will break your test, unless you mock everything at great expense of time and readability.

In short, a Map is a much better choice here than any Mockito mock.

Sign up to request clarification or add additional context in comments.

3 Comments

Actually I am mocking a call to an outside method(third party) that takes a map and I cant use any generics like anyMap or anyMapOf() because I have different calls for the same method with different query param.
I see! For future reference, what you're trying to do is not mocking a Map of arrays, it's matching a Map of arrays. (You may want to update your question.) Though Map supports equals, that will call Object[].equals, which won't match the way you want by default. I'll update my answer.
May be my question made a wrong impact. Now I Modified my question accordingly.
0

After struggling for the whole day, I figured out a solution. As specified by @Jeff Bowman, Mockito can't match Object to String in real call. I followed this link and wrote a custom matcher that can compare the real arguments to our specified, if matches returns true, then Mockito mocks the call otherwise false. This is that solution

      private class Query4ArgumentMatcher extends ArgumentMatcher<Map<String, Object[]>> {
     private String value;


    public Query4ArgumentMatcher(String value) {

        this.value = value;

    }

    public boolean matches(Object o) {

        if (o instanceof Map) {

            Map<String, Object[]> map = (Map<String, Object[]>) o;


            for (Map.Entry<String, Object[]> m : map.entrySet()) {

                String s = (m.getValue())[0].toString();

                if (value.contentEquals(s)) {

                    return true;

                }

            }

        }

        return false;

    }

}

and in unit test

 when(DB.get(eq("SomeString"), Mockito.argThat(new Query4ArgumentMatcher("entity1"))));

Hope this will help someone.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.