0

My class has a method with default parameters whose values are derived from an injected parameter:

class Slack @Inject constructor(
  private val brokerSettings: BrokerSettings
) {

  fun alert(
    message: String,
    responsible: String = brokerSettings.slack?.responsible ?: "<!here>",
    channels: List<String>? = brokerSettings.slack?.channels
  ) {
   ...
  }
}

I can construct a mockk<Slack>, but when my test tries to invoke alert(message) on it, relying on the default parameters, I get a null pointer exception in alert$default.

If I change brokerSettings.slack above to brokerSettings?.slack, then it works - but warns me that I don't need the extra ?.

I tried mocking the brokerSettings field's getter:

val slack = mockk<Slack>(relaxed = true)
{
//    every { [email protected] } answers { BrokerSettings() }
//    every { this getProperty "brokerSettings" } propertyType BrokerSettings::class answers { BrokerSettings() }
}

but [email protected] fails to compile if the field is private and throws an NPE just like the original problem if it's public, and this getProperty fails with a reflection error.

I'm bodging for now by setting the defaults to null and using ?: to conditionally compute the defaults inside the function, but I'd prefer not to do that.

Any ideas?

1 Answer 1

1

This is an edit/complete rewrite of my first answer. I obviously did pay too little attention to the real problem.

What you need to do is:

  • create a mock for the BrokerSettings instance
  • configure the mock to not fail on the slack getter
  • create a real Slack instance passing in the mock
  • wrap that Slack instance with a spy
  • configure that spy to not do anything real in the alert function
  • put the spy into your class under test
  • call the method you want to test
  • verify the slack.alert call

In Kotlin:

class SlackUsingClass(private val slack: Slack) {
    fun doIt(message: String) {
        slack.alert(message)
    }
}
import io.mockk.Runs
import io.mockk.every
import io.mockk.just
import io.mockk.mockk
import io.mockk.spyk
import io.mockk.verify
import org.junit.jupiter.api.Test

class SlackUsingClassTest {

    @Test
    fun `should call slack`() {

        val brokerSettings = mockk<BrokerSettings>()
        every { brokerSettings.slack } returns null

        val slack = spyk(Slack(brokerSettings))
        every { slack.alert(any(), any(), any())} just Runs

        val slackUsingClass = SlackUsingClass(slack)

        slackUsingClass.doIt("test")

        verify { slack.alert("test", any(), any()) }

    }
}
Sign up to request clarification or add additional context in comments.

3 Comments

As I say above, I'm mocking Slack to test a different class, and when that other class tries to execute slack.alert(message) then Slack.alert tries to evaluate brokerSettings to determine the default values of one of its parameters, and alert$default gets a NPE on the line defining the parameter default. So it doesn't make any difference to give any() to those parameters in the mocked call, because the NPE happens because the parameter's default is trying to evaluate an expression based on a constructor parameter which is never set (because mockk<> doesn't know to set it).
I rewrote my answer, I didn't look thoroughly enough at the problem before.
Nice! Thanks - I don't often use spies - tend to forget about them.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.