I’m wondering why I don’t see compilation errors f...
# kotest
w
I’m wondering why I don’t see compilation errors for assertions where types don’t match, for example
5 shouldBe "5"
. There’s no error shown, even though the signature would force the right-hand parameter to be a assignable to the left-hand one (
infix fun <T, U : T> T.shouldBe(expected: U?)
), or so I thought. Is this expected? It’s pretty annoying when refactoring code, because tests still compile but fail in runtime
I feel like I’m missing something obvious
I guess it falls back to
Any.shouldBe(other: Any)
?
j
Why it uses two generics instead of only T?
w
Probably to support things like
foo shouldBe fooSubtype
? But even with single generic parameter it seems
A() shouldBe C()
(no inheritance) works, because now
T
is just
Any
as well
I “worked around” this by creating a custom function in kotest package:
Copy code
infix fun Any.shouldBe(expected: Any?): Nothing = error("Unsupported")
but it only works for
shouldBe
, I’d have to have overloads for all the different matchers 😕
s
There's a reason for this but I can't remmeber atm. Something to do with shouldBe accepting matchers as well as types iirc
w
Is it even possible to have a signature that errors out if both
T
and
U
would be
Any
?
j
But for specific type there is a function
shouldBeTypeOf
I think
w
I don’t want to check just types. Here’s the issue: I had a test
doSomething() shouldBe 5
. Later on I change
doSomething
to return a string instead of an int. Normally it’s easy to find all the places to update by just trying to compile the project and navigating to each error
However,
shouldBe
will not result in an error. I still want to do some meaningful change, in this case check that
doSomething shouldBe "5"
, not just check a type
My issue is that Kotest assertions don’t support refactoring like that very well, because “obvious” errors like this only show up when running the tests. Unless I’m checking
Copy code
// foo: Any
// bar: Any
foo shouldBe bar
I don’t want
shouldBe
to resolve generic parameters to
Any
s
There is an annotation called onlyinputtypes
But not sure it's public
I agree with you lukasz but if it was an easy change I'd have done it :)
w
Yeah sure, originally I didn’t really understand why this happens, only later I realized it’s because type is
Any
. And I agree it’s not easy, other than overloading each matcher with a deprecated
Any.
variant I don’t have a clue what can be done
I may try creating a custom Android Lint check for our project, as it shows up in IDE
s
Not that 1.5 is out I will have another play
The compiler may be smarter now
j
@wasyl I know you don't want to check the type, what I mean is
shouldBe
should not compile if the type is different, and maybe a
relaxedShouldBe
or something so should be added
👌 1
w
OnlyInputTypes
might be helpful, although I don’t have it handy to check how it works exactly. But I see it in
kotlin-test
sources
s
It was private to jb previously
This topic has come up several times before and I'd love to fix it
w
Yep it’s still
internal
s
There's also the issue of subtyping on both sides
dog shouldBe animal
animal should be dog
w
Right 😕
shouldBe
is symmetric
Or, should be
s
Yep
Now we could add shouldEqual which isnt
Or something
w
Actually Javier had a point, why are there two types? Isn’t
shouldBe
only true when both sides are exactly the same type?
j
I think there is no problem when
animal shouldBe dog
not compiling
It is like avoid anticipating to the future
s
Both sides can be bounded to each other
CharSeq shouldBe String
String shouldBe CharSeq
j
First fails, second works
☝️ 1
And I like that
s
Right but the 5 years worth of code using it might not like it ;)
j
you force to have an standard in the order to check the things
s
The order is actual shouldBe expected
Things that previously compiled must continue to compile. I'd be happy to add shouldEqual or something like that which uses the lhs type as the upper bound and does not support arbitrary matchers.
j
In JUnit is expected/actual no?
s
not sure, but I just follow the english readability "my cat should be black"
error: your cat is purple
shouldEqual
would be trivial to implement and easy to do a find replace to update existing usages for @wasyl
j
but it is
myCat shouldBe black
is
String shouldBe CharSeq
myCat
knows what is color (inherence, property, or whatever), black is a color, it knows nothing about animals or cats
exactly the same that String and CharSeq
s
That's only the type defs though, the actual impls could be the same val cat = Cat("felix") val cat2: Animal = Cat("smokey") cat shouldBe cat2
You have a method that returns a super type and you want to test its the actual type you expect in your unit tests. We don't really want to cast in the test either.
j
that should compile, but cat2 shouldBe cat, should not compile
s
they should both compile imo
w
And what about overriding
Any.shouldBe
in a separate, opt-in artifact?
s
anyway the point is moot, because like I said, if we change it, we will break code for people who have been using kotest (and kotlintest) since 2016. So the best workaround is a new method that is stricter on its types.
j
the problem adding something like strictShouldBe it can be used as default in almost all the case
that is the reason I think it is worth shouldBe being strict, and having a relaxedShouldBe
s
That would be better, but it's not going to change in a backwards incompatible way now outside of a major major version bump
j
Yeah, maybe for 5.0 or so
w
Anyway the issue would be present for all kinds of matchers, like
aListOfFoo shouldContain bar
should fail as well
s
kotlin doesn't have higher kinded types so there's plenty of restrictions