MiamiBeach
MiamiBeach

Reputation: 3497

Assert two lists are equal in Spock framework

I test my application using Spock framework, the tests are written in Groovy.

As a result of some method evaluation I have a list of objects. I want to test if this list is the same as the list I expect. I have coded the following:

def expectedResults = [ ... ] //the list I expect to see
def isEqual = true;

when:
def realResults = getRealResultsMethod() //get real results in a list here
expectedResults.each {isEqual &= realResults.contains(it)}
then:
isEqual
0 * errorHandler.handleError(_) //by the way assert that my errorHandler is never called

This is one of my first experiences with Groovy, so may be I am missing something?

PS

What confuses me is 'equals' operator in Groovy and Spock. Given Java ArrayList or Java array, equals operator is simply identity operator: equals is ==. In Groovy as far as I understand default equals operator is really equals (form here: http://groovy.codehaus.org/Differences+from+Java). But what is 'equals' for Groovy List or Set?

UPDATE

To be more precise. I want to find out wether the two lists have the same objects, no extra objects for both list, the order doesn't matter. For example:

list=[1,5,8]

list1=[5,1,8]    
list2=[1,5,8,9]

println(list == list1) //should be equal, if we use == not equal    
println(list == list2) //should not be equal, if we use == not equal

Upvotes: 21

Views: 31269

Answers (5)

dominikbrandon
dominikbrandon

Reputation: 428

Since Spock 2.1 there's a much better option built-in, than the ones mentioned in other answers: =~ and ==~ operators. See more in the docs.

==~ does exactly what you wanted: 2 lists are equal only when they contain equal objects in the same amount, ignoring the order.

In your case:

def list = [1,5,8]

def list1 = [5,1,8]    
def list2 = [1,5,8,9]

list ==~ list1 // true
list ==~ list2 // false

Upvotes: 2

Opal
Opal

Reputation: 84844

If You just need to check if both lists have same elements You may try:

when:
    def expectedResults = [ ... ]
    def realResults = getRealResultsMethod()

then:
    realResults.size() == expectedResults.size()
    realResults.containsAll(expectedResults)
    expectedResults.containsAll(realResults)

But if You need to check if both lists are equal You just need (as in @tim_yates' response):

when:
    def expectedResults = [ ... ]
    def realResults = getRealResultsMethod()

then:
    realResults == expectedResults

Remember that two lists are equal only if they have the same elements in the same order.

Upvotes: 10

Roger Glover
Roger Glover

Reputation: 3266

The semantic data structure you are seeking is often referred to as a bag. In a bag, as in a set, order of elements does not matter. However, in a bag, as in a list, repeat elements are allowed. Bag equality, then, consists of having the same elements each in the same amounts, although not necessarily in the same order. So it seems that what you are looking for is a way to apply "bag" semantics to your list. The easiest way to do this is to duplicate one of the bags and remove the other bag's elements from the duplicate until:

  • all the other bag's elements are exhausted and the duplicate is empty (they're equal!)
  • all the other bag's elements are exhausted and the duplicate is NOT empty (they're different!)
  • during the iteration, one of the other bag's elements can't be removed from the duplicate (they're different!)

Something like the equals() implementation shown below:

class Bag {
    List list
    Bag(List list) { this.list = list }
    @Override boolean equals(that) {
        def thisList = list?.clone() ?: []
        that instanceof Bag &&
            (that?.list ?: []).every { thisList.remove((Object)it) } &&
            !thisList
    }
    @Override int hashCode() { this?.list?.sum { it?.hashCode() ?: 0 } ?: 0 }
    @Override String toString() { this?.list?.toString() }
}

def a = [1, 5, 1, -1, 8] as Bag
def b = [5, 1, -1, 8, 1] as Bag // same elements different order
def c = [1, 5, -1, 8]    as Bag // same elements different size
def d = [5, 5, 1, -1, 8] as Bag // same elements same size different amounts of each

assert a == b
assert a != c
assert a != d

println a    // [1, 5, 1, -1, 8]
println b    // [5, 1, -1, 8, 1]

Alternatively, if you don't care about the original list order at all, you can represent the bag as a map. The bag element values are the map keys are and the number of appearances of each bag element are the map values. At that point, equality is just map equality.

Like this:

class BagAsMap {
    Map map = [:]
    BagAsMap(List list) {
        (list ?: []).each { map[it] = (map[it] ?: 0) + 1 }
    }
    @Override boolean equals(that) {
        that instanceof BagAsMap && this?.map == that?.map
    }
    @Override int hashCode() { this?.map?.hashCode() ?: 0 }
    @Override String toString() {
        '[' + map.keySet().sum { k -> (0..<(map[k])).sum { "${k}, " } }[0..-3] + ']'
    }
}

def a1 = [1, 5, 1, -1, 8] as BagAsMap
def b1 = [5, 1, -1, 8, 1] as BagAsMap // same elements different order
def c1 = [1, 5, -1, 8]    as BagAsMap // same elements different size
def d1 = [5, 5, 1, -1, 8] as BagAsMap // same elements same size different amounts

assert a1 == b1
assert a1 != c1
assert a1 != d1

println a1
println b1

In any case this is serious overkill if you just need to check order-neutral list equivalence once or twice, but if bag semantics are a frequent need, then defining a Bag class in one of these two ways is probably a good idea.

As noted elsewhere, in this specific case, a.sort() == b.sort() is a sufficient stopgap in place of full bag semantics. However, not all objects that might be placed in a list together are mutually sortable, even with the most sophisticated comparator closure. However, they all have hashCode() and equals(), which is all that is needed for either bag implementation shown.

Furthermore, List.sort() has O(n log n) algorithmic complexity, whereas all the bag operations shown are O(n) complexity. Not worth worrying about for these small lists, but a much bigger deal for large ones.

Upvotes: 3

MiamiBeach
MiamiBeach

Reputation: 3497

list1.containsAll(list2) && list2.containsAll(list1)

Upvotes: -4

tim_yates
tim_yates

Reputation: 171144

Just do:

when:
    def expectedResults = [ ... ]
    def realResults = getRealResultsMethod()

then:
    realResults == expectedResults

Or, if you don't care about order (which is breaking the contract of List, but there you go), you could do:

then:
    realResults.sort() == expectedResults.sort()

Or convert them to sets or something

Upvotes: 34

Related Questions