How to call invoking on a setter?

Viewed 80

I want to write a test case that expects a setter to throw an exception.

class ClassUnderTest {

    fun regularMethod(){ require(false)}

    var currentValue: Int = 0
        set(_) {
            require(false)
        }
}

For a regular function I can simply do:

import org.amshove.kluent.*
import org.junit.jupiter.api.*
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.ValueSource

class Tests {

    @Test
    fun `debug throwing fun`() {
        val f = ClassUnderTest()
        invoking { f.regularMethod() } `should throw` IllegalArgumentException::class
    }

}

But for the setter I am a bit confused

    @Test
    fun `debug throwing setter`() {
        val f = ClassUnderTest()
        //1) doesn't compile: Expected a value of type Any?
        invoking { f.currentValue = 0 } `should throw` IllegalArgumentException::class

        //2) works, but why? Is this canonical?
        invoking { 0.also { f.currentValue = it } } `should throw` IllegalArgumentException::class

        //3 compiles but no exception thrown -> test fails
        invoking { { f.currentValue = 0 } } `should throw` IllegalArgumentException::class
    }

1) was my intuitive approach. Apparently there is no return value from a setter. Why not? have defined a function which is called, so I would expect the same behavior as a void method.

2) Android Studio suggested this one. Why does this now work? And is this the recommended way?

3) My own attempt, why doesn't this one work?

1 Answers

Look at what invoking needs as the argument:

fun invoking(function: () -> Any?): () -> Any?

It needs a function that returns anything.

Your lambda contains an assignment statement. The compiler, for one reason or another, is unable to figure out that if the type of the lambda is inferred to be () -> Unit, it can then be converted to () -> Any?. That is:

// here the compiler can infer that the lambda is () -> Unit
val x = { f.currentValue = 0 } 
// this now works
invoking(x) `should throw` IllegalArgumentException::class

However, if you ask the compiler to try to make { f.currentValue = 0 } to be of type () -> Any?, it suddenly becomes dumb and can't do it. I think this is because it's trying to find an expression that can be converted to Any? in your lambda, but finds a statement instead. I don't know if this is a bug or intended behaviour.

This also works:

invoking({ f.currentValue = 0 } as () -> Unit) `should throw` IllegalArgumentException::class

Interestingly, IntelliJ says that the cast is unnecessary, but it's not.

Anyway, the second way in your question works, because 0.also { ... } is an expression.

The third way doesn't work, because although { f.currentValue = 0 } is an expression, the lambda you pass to invoking now becomes something that doesn't throw. It becomes something that simply returns another lambda. You can do:

invoking { run { f.currentValue = 0 } } `should throw` IllegalArgumentException::class

Or

invoking { { f.currentValue = 0 }.invoke() } `should throw` IllegalArgumentException::class

Instead.

By the way, if invoking was instead declared like this:

fun <T> invoking(function: () -> T): () -> Any?

Then your "intuitive way" would have worked. There might be a reason why () -> Any? was used instead. I'm not the authors so I don't know.

Related