I've run into a funny/strange issue. Not sure it's...
# mockk
d
I've run into a funny/strange issue. Not sure it's strongly related to mockk but I've encountered it in that context. I'm using this for comparing recursive structures in tests (using AssertJ):
Copy code
fun <T> assertEqualsRecursive(actual: T, expected: T, vararg ignoringFields: String) {
    assertThat(actual)
        .usingRecursiveComparison().withStrictTypeChecking().ignoringFields(*ignoringFields)
        .isEqualTo(expected)
}
In my actual test case I'm capturing a list argument into a slot:
Copy code
val messageValuesSlot = slot<List<SomeType>>()
val actualMessages = messageValuesSlot.captured
and then comparing it with an expected list:
Copy code
assertEqualsRecursive(actualMessages, listOf(...))
The problem is that thanks to the
withStrictTypeChecking
option the comparison fails with
Copy code
actual and expected are considered different since the comparison enforces strict type check and expected type java.util.Arrays$ArrayList is not a subtype of actual type java.util.ArrayList
So if I understand it correctly • There are for some reason two
ArrayList
in java.util • The
listOf
constructor uses different
ArrayList
than the mockk when filling the
messageValuesSlot
Why is that? Why mockk doesn't use
listOf
? Is there anything I can do about that other than reverse-engineer mockk and use the same
ArrayList
?
e
one of those array lists is literally a List wrapper around an array, not resizable - that's what you get with asList(). the other has mutable size, capacity, array reference.
👍 1
s
Technically, yes, there are two classes in Java that are both called
ArrayList
. It's an unfortunate coincidence. One of them is the resizable
ArrayList
class we all know and love, and the other is a fixed-size list that's created by the
Arrays.asList
function. I'd guess that mockk is using the resizable version, maybe via
mutableListOf()
. Meanwhile,
listOf(elements)
just returns
elements.asList()
, which is nice and efficient. But these are all implementation details! You shouldn't ever need to care about the actual class that's returned by any of these functions. Using strict type checking to compare two lists isn't going to be useful, except in the unlikely event that you actually care about specific implementation details of the list itself.
Maybe you could use
usingRecursiveFieldByFieldElementComparator
instead?
I think that will let you turn on strict type checking for the elements without comparing the types of the actual lists.
d
Meanwhile, I've tracked down the possible root cause: https://kotlinlang.slack.com/archives/C0B8MA7FA/p1717663792986949
In other words, this is indeed not related to mockk at all. I just happen to use the
+
operator in my code whereas I'm comparing the result with a "plainly" constructed list in my test.
Using strict type checking to compare two lists isn't going to be useful
I agree about that but I wanted to create a universal function
assertEqualsRecursive
that could be correctly used in any case. Actually, I've been using such a function for a while now but I was missing the
withStrictTypeChecking
option which lead to issues such as
Err(SomeError)
and
Err(OtherError)
were considered equal (
SomeError
and
OtherError
are data objects). So now I have fixed a false positive but encountered a false negative at a different place 🙂
s
🤔 if
Err(SomeError)
and
Err(OtherError)
are considered equal then it sounds like you have a bug in your
equals
implementation for the
Err
class…
d
No, I (actually the kotlin-result library I'm using) do not.
Copy code
Err(SomeError).equals(Err(OtherError))
is false. I'm probably missing another piece of config of
assertThat
. Let me check.
Indeed, the overridden equals is ignored by default. The corresponding switch is
usingOverriddenEquals
.
s
Oh I see, this is in conjunction with the recursive comparison. I can see why that would ignore
equals
by default. I hadn't considered that two data objects would be considered equal when doing a recursive comparison like that, but it makes sense! After all, their only distinguishing feature is their type.
d
that two data objects would be considered equal when doing a recursive comparison
Yeah, it also wasn't totally obvious to me at first 🙂
Anyway, honoring
equals
sadly breaks other cases 😞 They involve data classes and the
ignoringFields
feature. Not sure yet which is the culprit.
But I guess that
ignoringFields
and
usingOverriddenEquals
are simply incompatible because by using
equals
for the comparison you are completely delegating the comparison to the class method.
So it seems that my original idea of having a universal assert is doomed to failure 😞
Perhaps I would need something like "use overriden equals but not for data classes" 🙂
s
I wonder if an assertion library that's designed for use with Kotlin would have better handling for Kotlin's data classes and data objects? I haven't tried using assertJ with Kotlin.
d
Hopefully, I'm using AssertJ because it still has the most features when compared with other assertion libs (especially the ones I need in my universal assert). Or I should say it used to have 2 years ago - perhaps now libraries like kotest have become mature enough. Time to look into it again!