Question

I have a dependency with a method that takes a Map as an argument.

public interface Service {
    void doSomething(Map<String, String> map);
}

I'd like to write an assertion that this dependency is called with appropriate map contents. Something like this:

@RunWith(JMock.class)
public class MainTest {
    private Mockery context = new Mockery();
    private Service service = context.mock(Service.class);
    private Main main = new Main(service);

    @Test
    public void test() {
        context.checking(new Expectations(){{
            oneOf(service).doSomething(with(hasEntry("test", "test")));
        }});
        main.run();
    }
}

Unfortunately, this fails to compile, since hasEntry has wildcards in map generic parameters:

public static <K, V> org.hamcrest.Matcher<java.util.Map<? extends K, ? extends V>> hasEntry(K key, V value);

Is there any way to write a JMock expectaion for map contents?

Was it helpful?

Solution

There isn't a good answer to this as we hit the limits of Java generics. There's a tension between the generics we need for jMock and what we need for assertThat()

I tend to add a helper method, with an expressive name, to force the types.

@Test public void test() {
  context.checking(new Expectations(){{
    oneOf(service).doSomething(with(mapIncluding("test", "test")));
  }});

  main.run();
}

@SuppressWarnings({"unchecked", "rawtypes"})
private Matcher<Map<String, String>> mapIncluding(String key, String value) {
   return (Matcher)Matchers.hasEntry(key, value);
};

Yes, this is pig-ugly. I can only apologise that this is the best we appear to be able to do. That said, it's rare that I have to go as far as turning off the types, I can give it a name that's meaningful in the domain, and I've localised the unchecking to the helper method.

OTHER TIPS

I ended up creating a method specify() that allows downcasting of generic matchers to more specific ones

public static <T> Matcher<T> specify(final Matcher<? super T> matcher) {
    return new TypeSafeMatcher<T>() {
        @Override
        protected boolean matchesSafely(T item) {
            return matcher.matches(item);
        }

        @Override
        public void describeTo(Description description) {
            matcher.describeTo(description);
        }
    };
}

Using this method I can downcast any existing generic matcher, like hasEntry()

public <K, V> Matcher<Map<? extends K, ? extends V>> hasEntry(K key, V value)

to a more specific one in a generic-safe manner, like this:

private static <K,V> Matcher<Map<K, V>> aMapHavingEntry(K key, V value) {
    return specify(hasEntry(key, value));
}

Now I can use this specific matcher as an expectation parameter:

    context.checking(new Expectations() {{
        oneOf(service).doSomething(with(aMapHavingEntry("test", "test")));
    }});

Using specify() method I created a bunch of specific matchers for most popular interfaces: Map, Collection, List, Set, like:

private static <K,V> Matcher<Map<K, V>> aMapHavingEntry(K key, V value) {
    return specify(hasEntry(key, value));
}

private static <K> Matcher<Collection<K>> aCollectionContainingInAnyOrder(K... items) {
    return specify(containsInAnyOrder(items));
}

I also suggested adding the same functionality to JMock, though all I got was silence.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top