Ritabrata
Ritabrata

Reputation: 119

How can we mock scala method with generic return type and implicit parameters?

I have a configuration fetcher that looks something like this.

def getForCountry[A](path: String, fallbackToDefault: Boolean)
                  (implicit loader: ConfigLoader[A], ac: AppContext): A = {
configuration.getOptional[A](s"${ac.country}.$path") match {
  case Some(value)                =>
    value
  case None if fallbackToDefault  =>
    configuration.get[A](path)
  case None if !fallbackToDefault =>
    throw new RuntimeException(s"${ac.country}.$path key not found in configuration")
}

The invocation of the same method is as follows -

val countrySpecificConfig =
  configurationHelper.getForCountry[Map[String, String]]("googleCloudPlatform.jobConfig.demandBasedPricing", fallbackToDefault = false)

Now I want to mock the getForCountry method in my unit tests -

when(configurationHelper
    .getForCountry[Map[String, String]]("googleCloudPlatform.jobConfig.demandBasedPricing", fallbackToDefault = false))
    .thenReturn(countryPricingWeekConfiguation)

Surprisingly it seems that this expectation is not set up properly. On executing the test the mock returns null.

Any clues on how to proceed with this? Please feel free to let me know if you need any further details.

Upvotes: 0

Views: 1366

Answers (3)

ultrasecr.eth
ultrasecr.eth

Reputation: 1497

Have you tried mockito-scala? if you use the new syntax the implicits will be taken care of automatically (assuming you use the idiomatic syntax and the same instances are resolved in the test and in your prod code)

Also even if you use the traditional syntax your stub would be reduced to

when(configurationHelper
    .getForCountry[Map[String, String]]
    (eqTo("googleCloudPlatform.jobConfig.demandBasedPricing"), eqTo(false))(*, *)
    .thenReturn(countryPricingWeekConfiguation)

or with the idiomatic syntax

configurationHelper.getForCountry[Map[String, String]]
    ("googleCloudPlatform.jobConfig.demandBasedPricing",false)
    shouldReturn countryPricingWeekConfiguation

or if the implicits are not the same in test and prod (notice I can also mix arg matchers like * and raw params like 'false')

configurationHelper.getForCountry[Map[String, String]]
    ("googleCloudPlatform.jobConfig.demandBasedPricing",false)(*,*)
    shouldReturn countryPricingWeekConfiguation

Upvotes: 1

Ritabrata
Ritabrata

Reputation: 119

Thanks a ton Pritam. The following code seems to work.

when(configurationHelper
    .getForCountry[Map[String, String]]
    (ArgumentMatchers.eq("googleCloudPlatform.jobConfig.demandBasedPricing"), ArgumentMatchers.eq(false))
    (ArgumentMatchers.any[ConfigLoader[Map[String, String]]](), ArgumentMatchers.any[AppContext]()))
    .thenReturn(countryPricingWeekConfiguation)

Upvotes: 0

Pritam Kadam
Pritam Kadam

Reputation: 2527

I strongly doubt that different instances of implicit ConfigLoader and AppContext are getting passed in your actual method invocation and mocked one. If you are using intellij, verify which implicits are getting passed by enabling them. To enable them, press ctr+alt+shift++

Here is the complete tests simulates your situation which works just fine:

test("mock example") {
    trait ConfigLoader[T] {}

    trait AppContext { def country: String }

    trait ConfigurationHelper {
      def getForCountry[A](x: String, fallbackToDefault: Boolean = true)(implicit loader: ConfigLoader[A], ac: AppContext): A
    }

    implicit val loader: ConfigLoader[Map[String, String]] = mock[ConfigLoader[Map[String, String]]]
    implicit val ctx: AppContext                           = mock[AppContext]
    val configurationHelper                                = mock[ConfigurationHelper]

    val mockedResult = Map("x" → "1")

    when(
      configurationHelper
        .getForCountry[Map[String, String]]("googleCloudPlatform.jobConfig.demandBasedPricing", fallbackToDefault = false)
    ).thenReturn(mockedResult)

    val countrySpecificConfig =
      configurationHelper
        .getForCountry[Map[String, String]]("googleCloudPlatform.jobConfig.demandBasedPricing", fallbackToDefault = false)

    countrySpecificConfig.foreach(println)
  }

// =========================== Output ====================
// (x,1)

Upvotes: 1

Related Questions