Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Assert two lists are equal in Spock framework

Tags:

java

groovy

spock

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
like image 634
MiamiBeach Avatar asked Oct 09 '14 16:10

MiamiBeach


People also ask

How do you assert two lists are equal?

A simple solution to compare two lists of primitive types for equality is using the List. equals() method. It returns true if both lists have the same size, and all corresponding pairs of elements in both lists are equal.

How do you check if two lists are equal in JUnit?

Using JUnit We can use the logic below to compare the equality of two lists using the assertTrue and assertFalse methods. In this first test, the size of both lists is compared before we check if the elements in both lists are the same. As both of these conditions return true, our test will pass.


3 Answers

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

like image 104
tim_yates Avatar answered Oct 24 '22 07:10

tim_yates


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.

like image 37
Opal Avatar answered Oct 24 '22 09:10

Opal


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.

like image 3
BalRog Avatar answered Oct 24 '22 09:10

BalRog