assertk icon indicating copy to clipboard operation
assertk copied to clipboard

Generalise containsExactly to be an extension of Assert<Iterable<*>> instead of Assert<List<*>>

Open adyang opened this issue 4 years ago • 2 comments

Currently, containsExactly only works on List Assertion:

val list = listOf("one", "two", "three")
assertThat(list).containsExactly("one", "two", "three")

It does not compile if used on an Iterable (which seems inconsistent, because all the other contains* work on Iterable:

val iterable = listOf("one", "two", "three") as Iterable<String>
assertThat(iterable).containsExactly("one", "two", "three") // does not compile

Would it be possible to change containsExactly to be an extension of Assert<Iterable<*>>?

adyang avatar May 26 '21 12:05 adyang

There concern here is that containsExactly() cares about the order of the elements, but not all iterable collections have a stable order. For example:

assertThat(hashSetOf("one", "two")).containsExactly("one", "two")

would fail, quite surprisingly.

Unfortunately there's not generic interface for 'has stable order' so List is the next-best thing. The down-side of course is if you have a custom collection with a stable order but doesn't implement List than you don't get this, but I think those cases would be quite rare?

evant avatar Jun 09 '21 03:06 evant

Oh, i see, we want to prevent users from doing the wrong thing in case the Iterable does not have a stable iteration order. Is this the right understanding?

I was seeing it from the perspective of what an Iterable is:

Classes that inherit from this interface can be represented as a **sequence** of elements that can be iterated over.

I was thinking since it can be represented as a sequence, it should make sense that there is an order (stable or not) and containsExactly should work on it.

Also, there are other standard kotlin collection builders that are Iterable and not lists (might be uncommon but depends on use case):

assertThat(sortedSetOf("one", "two")).containsExactly("one", "two") // use case: sorted order but no duplicates
assertThat(linkedSetOf("one", "two")).containsExactly("one", "two") // use case: insertion order, no duplicates

I guess this would be a trade-off between preventing users from doing the wrong thing vs making it convenient/ flexible for the generic case. I was thinking because this is a library, it might be useful to make it as generic as possible.

That said, there are workarounds for both cases, so we can document them regardless of the decision: If containsExactly is on Iterable, the example would eventually fail and users should use the appropriately named containsOnly/ containsExactlyInAnyOrder:

assertThat(hashSetOf("one", "two")).containsOnly("one", "two")

If containsExactly remains on List, an example such as this would help if anyone stumbles on the question of "Why does containsExactly only work on a List? The operation should make sense even if the collection is not indexed":

// Design decision to make the common case less error prone but the uncommon case still possible
assertThat(sortedSetOf("one", "two").toList()).containsExactly("one", "two")
assertThat(linkedSetOf("one", "two").toList()).containsExactly("one", "two")

adyang avatar Jun 10 '21 12:06 adyang