Why are my sets of data objects not equal?


#1

I was just surprised by a failing test assert (using kotlin.test here):

data class MyType {
    var values : MutableSet<OtherType> = mutableSetOf()
}
data class OtherType(val value: String) {}

assertEquals(setOf(OtherType(myValue)), myObject.values)

The error looks like this:

expected: java.util.Collections$SingletonSet<[OtherType(value=myvalue)]> but was: java.util.LinkedHashSet<[OtherType(value=myvalue)]>

Clearly, the test should have been successful: I only care about value equality and not at all about which set implementation is used.

How do I express this best in Kotlin?

Ideas

  • Use Guava: assertTrue(Sets.symmetricDifference(setOf(OtherType(myValue)), myObject.values).isEmpty())
    rather lengthy, though, and un-mathy colleagues might not understand it
  • Create a new assertion function like assertEqualContent for comparing sets by content.
  • Re-define == on sets (or all collections?): it should ignore the concrete type of the set itself.

For now, I went with 2 implemented using 1. A solution without Guava would be nice, though.


#2

You code is incomplete and even doesn’t compile. Cannot help you until more info.


#3

assertEquals compares these sets as equal in the code like yours, same as the equality operator.

import kotlin.test.assertEquals

fun main(args: Array<String>) {
//sampleStart
    data class OtherType(val value: String) {}
    class MyType {
        var values : MutableSet<OtherType> = mutableSetOf()
    }
    val myValue = "test"
    val myObject = MyType().apply { values.add(OtherType(myValue)) }
    
    println(setOf(OtherType(myValue))::class)
    println(myObject.values::class)
    println(setOf(OtherType(myValue)) == myObject.values)
    assertEquals(setOf(OtherType(myValue)), myObject.values)
//sampleEnd
}

So the reason why you see the other result must be something else.


#4

Ahh… that is what I get for not actually running the MWE. My bad; the setup looked too obvious. Should’ve learned that lesson a while ago. :'D

Here’s an actual MWE; it’s not about the sets at all:

data class OtherType(val value: String) {
    // Set by GORM
    var id: Long? = null
    var version: Int? = null

    override fun equals(other: Any?): Boolean {
        return when (other) {
            !is OtherType -> false
            id == other.id && value == other.value -> true
            else -> false
        }
    }
}

fun main(args: Array<String>) {
    val myValue = "test"
    println(OtherType(myValue) == OtherType(myValue))
}

Now that I actually looked at OtherType.equals, the problem is apparent – if you’ve seen Kotlin Puzzlers 2018 (code). It only looks correct (might be a Swift-ism). With

override fun equals(other: Any?): Boolean {
    return when (other) {
        is OtherType -> id == other.id && value == other.value
        else -> false
    }
}

everything works as expected, including set comparison.

Thanks for your help! (And for submitting that puzzler to Anton!)


#5

Oh, yeah. That one looks familiar :joy: