Custom Hamcrest matcher to compare maps I am new to Java and have written a custom matcher to compare two maps because the error message from either JUnit or Hamcrest is not detailed enough. The code I wrote works, but I welcome critiques or comments to make it better.

One question:

Right now, I hard-coded my map to &lt;String, String&gt; but would like to be more generic. My first attempt was to use &lt;Object, Object&gt;, which resulted in a compiler error.

Here is my matcher:

package net.southeastwind.test;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.hamcrest.Description;
import org.hamcrest.TypeSafeDiagnosingMatcher;


public class MapEquivalent extends TypeSafeDiagnosingMatcher&lt;Map&lt;String, String&gt;&gt; {
    private Map&lt;String, String&gt; m_expected;

    public MapEquivalent(Map&lt;String, String&gt; expected) {
        m_expected = expected;
    }

    public static MapEquivalent mapEquivalent(Map&lt;String, String&gt; expected) {
        return new MapEquivalent(expected);
    }

    @Override
    public void describeTo(Description description) {
        description.appendText("Maps are equivalent");
    }

    @Override
    protected boolean matchesSafely(Map&lt;String, String&gt; actual, Description description) {
        boolean matched = true;

        Set&lt;String&gt; keys = new HashSet&lt;&gt;();
        keys.addAll(actual.keySet());
        keys.addAll(m_expected.keySet());

        for (String key: keys) {
            String expectedValue = m_expected.get(key);
            String actualValue = actual.get(key);

            if (null == expectedValue) {
                matched = false;
                description
                    .appendText("\n\t\tOnly in actual: {").appendValue(key)
                    .appendText(": ").appendValue(actualValue).appendText("}");
            } else if (null == actualValue) {
                matched = false;
                description
                    .appendText("\n\t\tOnly in expected: {").appendValue(key)
                    .appendText(": ").appendValue(expectedValue).appendText("}");
            } else if (!actualValue.equals(expectedValue)) {
                matched = false;
                description
                    .appendText("\n\t\tValues differ: ")
                    .appendText("actual={").appendValue(key).appendText(": ").appendValue(actualValue).appendText("}")
                    .appendText(", expected={").appendValue(key).appendText(": ").appendValue(expectedValue).appendText("}");

            }
        }

        return matched;
    }
}


Sample test and output:

@Test
public void compareMaps() {
    Map&lt;String, String&gt; expected = new HashMap&lt;String, String&gt;() {{
        put("alias", "haiv");
        put("uid", "501");
        put("admin", "no");
    }};

    Map&lt;String, String&gt; actual = new HashMap&lt;String, String&gt;() {{
        put("alias", "haiv");
        put("uid", "502");
        put("shell", "bash");
    }};

    assertThat("Error 6a3429f7", actual, is(mapEquivalent(expected)));
    // CustomMatchersTest &gt; compareMaps FAILED
    //     java.lang.AssertionError: Error 6a3429f7
    //     Expected: Maps are equivalent
    //          but:
    //                 Values differ: actual={"uid": "502"}, expected={"uid": "501"}
    //                 Only in actual: {"shell": "bash"}
    //                 Only in expected: {"admin": "no"}
}
Yeah, the output looks really Nice :-)

Consider the following points:


I think you'll have NPE's when you pass null maps
There's a lot of different implementations of Map. If you have a SortedMap, it can pass, even though the sorting is different.
How does the message look, when the maps do not have the same size? Is the message clear enough?
The message "Expected: Maps are equivalent, but" is a bit strange? They are not equivalent, are they?
m_expected: Naming convention: should be mExpected. But to be honest, I'd go with expected.
Sadly, it only works with String...


Hope this helps...