15

In test we usually have assertNotNull, but it does not perform smart cast from a nullable type to a non-nullable. I have to write something like this:

if (test == null) {
    Assert.fail("")
    return
}

Is it a workaround to perform smart cast only with assertNotNull call? How do you deal with it?

Lior Bar-On
  • 10,784
  • 5
  • 34
  • 46
Boris
  • 4,944
  • 7
  • 36
  • 69
  • Can you provide a more complete example of unit test that you'd like to improve? I find it easier to use `!!.otherProperty` to assert that the receiver should not be null. – miensol Feb 05 '17 at 10:40
  • 1
    @miensol, with `!!.otherProperty` you get an NPE, and the test result will be an error, not a failed assertion. Not a big deal though. – hotkey Feb 05 '17 at 16:00
  • Force-unwrapping properties is unnecessary in tests written in Kotlin. See my answer in this thread [here](https://stackoverflow.com/a/64891834/1071320) which shows how to use the `kotlin-test` library's `assertNotNull` method to remove the need to force unwrap. – Adil Hussain Nov 18 '20 at 11:08

3 Answers3

13

Unfortunately, the bodies of the functions that you call, including inline functions, are not used for smart casts and nullability inference.

There's not much in your code that can be improved, and I would only suggest one thing: you can use the Elvis operator with a Nothing function for those assertion statements. The control flow analysis takes into account the branches resulting into Nothing and infers nullability from that:

fun failOnNull(): Nothing = throw AssertionError("Value should not be null")

val test: Foo? = foo()

test ?: failOnNull()
// `test` is not-null after that

This can be written without a function as well: test ?: throw AssertionError("..."), because a throw expression also has type Nothing.


Speaking of a more general case of failing an assertion, one might use a fail(...): Nothing function, which provides a bonus hint for the control flow analysis as well. The JUnit Assert.fail(...) is not a Nothing function, but you can find one in the kotlin-test-junit module or write your own one.

test as? SomeType ?: fail("`test` should be an instance of SomeType")
// smart cast works here, `test` is `SomeType`
hotkey
  • 140,743
  • 39
  • 371
  • 326
  • 1
    You can also do `test ?: fail("...")` – mfulton26 Feb 06 '17 at 14:39
  • 1
    @mfulton26, but the JUnit `Assert.fail()` is not a `Nothing` function, therefore there will be no nullability inference from this statement. Or is there a Kotlin testing library shipped with a `fail(): Nothing` function? – hotkey Feb 06 '17 at 14:47
  • 1
    `kotlin.test.fail` from `kotlin-test` (transitive dependency of `kotlin-test-junit`). I suppose the SO may not be using `kotlin-test` though so that is a good point. – mfulton26 Feb 06 '17 at 14:54
6

The kotlin.test library comes with an easy solution for this:

kotlin.test.assertNotNull()

Because this function implements Kotlin contracts it supports smart casting:

contract { returns() implies (actual != null) }

Example:

    fun Foo?.assertBar() {
        assertNotNull(this)
        assertEquals(this.bar, 0)
    }

Just make sure to use the right assertNotNull import (import kotlin.test.assertNotNull)!

If you don't already use the kotlin.test library, add it to your project:

group: 'org.jetbrains.kotlin', name: 'kotlin-test', version: '1.3.11

Rolf ツ
  • 8,611
  • 6
  • 47
  • 72
0

Big thank you to @Rolf for pointing me to the assertNotNull method offered by the kotlin-test library in his answer here.

I just want to add and point out that this method has a non-null return type (i.e. it returns the nullable object that is passed into it as a non-null object). So if you are force unwrapping properties in your test, you can move away from this and improve your tests as follows:

val myProperty = assertNotNull(myObject?.myProperty)
assertEquals("Foo", myProperty.someOtherProperty)
Adil Hussain
  • 30,049
  • 21
  • 112
  • 147