https://kotlinlang.org logo
#getting-started
Title
# getting-started
d

David Kubecka

03/13/2023, 4:27 PM
[testing] What's the best way in tests to compare nested collections disregarding the element order? e.g.
mapOf(1 to [2,3])
and
mapOf(1 to [3,2])
should compare the same.
j

Joffrey

03/13/2023, 4:28 PM
If order doesn't matter in the test, why should it matter in production?
d

David Kubecka

03/13/2023, 4:28 PM
The inner list is returned from a 3rd party.
e

ephemient

03/13/2023, 4:29 PM
map the inner list to a set
4
j

Joffrey

03/13/2023, 4:31 PM
The inner list is returned from a 3rd party.
So if you don't care about the order of the things returned by the third party lib, you could map the list to a set at that point (not in tests)
d

David Kubecka

03/13/2023, 4:32 PM
good point
But still, is there such a way to ignore the order? I'm not sure every situation can be solved like that 🙂
Also what about other "advanced" matchers such as ignoring property values in an instance.
j

Joffrey

03/13/2023, 4:39 PM
The choice of data structure is what expresses the constraints (order or not, unicity of elements, etc.) in production code, so I would rely on that pretty much in all situations.
what about other "advanced" matchers such as ignoring property values in an instance
That could also be expressed in production code if it doesn't matter in production, by customizing equals or putting a subset of data class properties outside of the constructor. If some properties use some randomness (like randomly generated IDs), maybe you could inject some fixed seed in tests to solve that. If none of the above works, you can convert both sides of the
assertEquals()
to some other representation that matches what you want to take into account (mapping instances to some other type with a subset of the properties, mapping lists to sets, this kind of things)
But I'm not aware of any mechanism (at least in the language) to change the behaviour of
equals()
. I'm not sure what you're looking for exactly.
c

CLOVIS

03/13/2023, 4:43 PM
It's part of the definition of what a
List
is that order is important. As long as it's a list, everything in the language will take the order of account.
Set
, however, does not declare that the order is important. Some implementations ignore the order (
HashSet
), some take it into account (
SortedSet
).
d

David Kubecka

03/13/2023, 4:49 PM
If some properties use some randomness
A classic example would be DB generated ids. The method under test creates some data and saves it to DB. I'm then checking the content of the entities. Of course, I can either: • split the code to generating and saving parts and test only the generating part • set up specific DTO just for the test use case But both of them seem to be too much work for a simple task of ignoring a few properties.
c

CLOVIS

03/13/2023, 4:50 PM
It's not that much work:
Copy code
assertEquals(expected, actual.mapValues { _, v -> v.toSet() })
d

David Kubecka

03/13/2023, 4:52 PM
@CLOVIS I'm now talking about the other part
ignoring property values in an instance.
Sorry...
c

CLOVIS

03/13/2023, 4:53 PM
Ah yes, ignoring values requires creating your own data structures.
j

Joffrey

03/13/2023, 4:53 PM
☝️ the code Ivan wrote is what I meant by you can convert both sides of the assertEquals() to some other representation that matches what you want to take into account It's the same for list->set as it is for replacing an instance with another instance with a fixed value
assertEquals(expected, actual.copy(id = "fixed_value"))
d

David Kubecka

03/13/2023, 4:54 PM
Sure you can do that but that gets quite annoying once the structure has more levels of nesting.
Btw I'm doing exctly that ^ currently and I don't like it so that's the reason why I'm asking 🙂
j

Joffrey

03/13/2023, 4:55 PM
In your specific example above, you control the DB mock if you're testing a method that creates and inserts instances, so the DB mock could generate fixed IDs
I'm still not sure what you are looking for. How do you envision the code that you would like to write for this? Are you looking for a way to locally change the meaning of
equals
for a given type?
Sure you can do that but that gets quite annoying once the structure has more levels of nesting
True, that's why I usually prefer to do it "right" and fix the production code instead 🙂
d

David Kubecka

03/13/2023, 4:59 PM
I would like to do something like
Copy code
val expected = MyClass(prop1 = 1, prop2 = 2, prop3 = ignore())
val actual = MyClass(prop1 = 1, prop2 = 2, prop3 = 3)

expectThat(expected).equals(expected)
It's basically the same as the "fixed value" approach but it IMO expresses the intent in a much clearer way.
c

CLOVIS

03/13/2023, 5:05 PM
That's not possible in standard Kotlin. #kotest allows creating partial matchers, but it's a bit more verbose than that. Other than that, you'd have to build your own thing.
d

David Kubecka

03/13/2023, 5:10 PM
Ok, thank you all!
s

Szymon Jeziorski

03/15/2023, 9:24 AM
Assuming you're on JVM and are open to Java libraries, you may want to use AssertJ. You can use recursive comparison ignoring collection order to achieve what you need regardless of how many nested levels there are, without manual mappings. Overall, AssertJ has a very robust API which can suit many edge cases
Copy code
val first = mapOf(1 to listOf(2, 3, mapOf("first" to listOf("1", "2", "3"))))
val second = mapOf(1 to (listOf(mapOf("first" to listOf("3", "1", "2")), 3, 2)))

assertThat(first)
    .usingRecursiveComparison()
    .ignoringCollectionOrder()
    .isEqualTo(second) // this would pass
13 Views