Search code examples
kotlinnullpointerexceptionmockingmockkdefault-parameters

How to mockk when a method's default parameters need an injected field?


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 { this@mockk.brokerSettings } answers { BrokerSettings() }
//    every { this getProperty "brokerSettings" } propertyType BrokerSettings::class answers { BrokerSettings() }
}

but this@mockk.brokerSettings 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?


Solution

  • 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()) }
    
        }
    }