4

I am trying to write a custom domain related assert/matcher in spock or hamcrest, but I am not sure how to proceed.

I tried writing a custom Matcher in hamcrest but so far that has only led me to a partial solution.

I am looking for some guidance as to what the proper course in this scenario would be.

Domain Objects:

  • ResultMap has an object Map < ILine, IResult > - There is one INodeResult associated with each ILine.
  • IResult has 4 (Google Guava) multimap objects that need to verified.

What I would like to do in my spock test is something like:

expect:
  that actualResultMap, matchesInAnyOrder(expectedResultMap)
  or
  that actualResultMap, matches(expectedResultMap) // Will only match if everything is in the same order.

The internal code will then evaluate each entry and do the appropriate tests on the inner objects as well.

So far I managed to write a part of the code that evaluates one set of multimap, but I am not sure how to chain my tests.

Custom Matcher:

package com.ps.DE.Test.CustomMatcher

import org.hamcrest.BaseMatcher

class MultimapMatcher {

    /**
     * Checks all the entries in a Multimap with another
     * @param expected
     * @return Shows the failure only if the entries do not match or are not in the same order
     */
    static hasAllInOrder(final com.google.common.collect.Multimap expected){
        [
                matches: { actual ->
                    for(key in actual.keySet()){
                        if (actual.get(key) != expected.get(key)){
                            return false
                        }
                    }
                    return true
                },
                describeTo: { description ->
                    description.appendText("MultiMap entries to be ${expected}")
                },
                describeMismatch: { actual, description ->
                    description.appendText("were ${actual}")
                }
        ]   as BaseMatcher
    }

    /**
     * Checks all the entries in a Multimap with another
     * @param expected
     * @return Shows the failure only if the entries do not match
     */
    static hasAllInAnyOrder(final com.google.common.collect.Multimap expected){
        [
                matches: { actual ->
                    for(key in actual.keySet()){
                        if (!actual.get(key).containsAll(expected.get(key))) {
                            return false
                        }
                    }
                    return true
                },
                describeTo: { description ->
                    description.appendText("MultiMap entries to be ${expected}")
                },
                describeMismatch: { actual, description ->
                    description.appendText("were ${actual}")
                }
        ]   as BaseMatcher
    }
}

Testing the custom Matcher:

package com.ps.DE.Test.CustomMatcher

import com.google.common.collect.ArrayListMultimap
import com.google.common.collect.Multimap
import spock.lang.Specification

import static com.ps.DE.Test.CustomMatcher.MultimapMatcher.hasAllInAnyOrder
import static com.ps.DE.Test.CustomMatcher.MultimapMatcher.hasAllInOrder
import static org.hamcrest.Matchers.not
import static spock.util.matcher.HamcrestSupport.that


class MultimapMatcherSpec extends Specification {

    def "Test hasAllInOrder"() {
        def actual = ArrayListMultimap.create();

        // Adding some key/value
        actual.put "Fruits", "Apple"
        actual.put "Fruits", "Banana"
        actual.put "Fruits", "Pear"
        actual.put "Vegetables", "Carrot"

        Multimap<String, String> expected = ArrayListMultimap.create();

        // Adding some key/value
        expected.put("Fruits", "Apple");
        expected.put("Fruits", "Banana");
        expected.put("Fruits", "Pear");
        expected.put("Vegetables", "Carrot");

        expect:

        that actual, hasAllInAnyOrder(expected)
        that actual, hasAllInOrder(expected)
    }

    def "Test hasAllInAnyOrder"() {
        Multimap<String, String> actual = ArrayListMultimap.create();

        // Adding some key/value
        actual.put("Fruits", "Apple");
        actual.put("Fruits", "Banana");
        actual.put("Fruits", "Pear");
        actual.put("Vegetables", "Carrot");

        Multimap<String, String> expected = ArrayListMultimap.create();

        // Adding some key/value
        expected.put("Fruits", "Banana");
        expected.put("Fruits", "Apple");
        expected.put("Fruits", "Pear");
        expected.put("Vegetables", "Carrot");

        expect:
        that actual, hasAllInAnyOrder(expected)
        that actual, not(hasAllInOrder(expected))
    }
}

Any help or guidance will be greatly appreciated.

Pranav Shah
  • 3,233
  • 3
  • 30
  • 47
  • 1
    Where are you stuck? Do your current matchers work? Suggestion: accept a matcher factory method to create matchers internally to be applied to the pairs of entries. That would make the overall MultiMap matcher more generic and allow using existing list matchers to be reused without creating new subclasses. – David Harkness Jun 27 '14 at 01:57
  • @DavidHarkness Thanks for the suggest. Could you point me to an example to do this properly? – Pranav Shah Jun 27 '14 at 03:59
  • I would have a look at the Hamcrest source code for the `CoreMatchers` class. Very enlightening. – BalRog Oct 17 '14 at 18:35

1 Answers1

0

Why do you need custom matchers at all? Perhaps, Spock and Groovy are powerful enough to fulfil your need without custom matchers.

To match two Multimap objects have the same content in the same order it is enough to do:

expected:
actual == expected

Is there any benefit in adding much more code to the same assertion?

For match in any order it's a bit more tricky but it's enough to add sorting method (may be extracted and reused within other test classes). This could look like:

expected:
sortValues(actual) == sortValues(expected)

and the method itself:

static Map<String, Collection<String>> sortValues(Multimap<String, String> multimap) {
    multimap.asMap().collectEntries {
        [it.key, it.value.sort(false)]
    }
}

Perhaps for better specification readability sortValues() may have name anyOrder() which would make assertion look like:

expect:
anyOrder(actual) == anyOrder(expected)

The internal code will then evaluate each entry and do the appropriate tests on the inner objects as well.

This part can be implemented by equals method on each object type under comparison.

topr
  • 4,482
  • 3
  • 28
  • 35