How to stub a method call with an implicit matcher in Mockito and Scala
Asked Answered
I

2

14

My application code uses AService

trait AService {
    def registerNewUser (username: String)(implicit tenant: Tenant): Future[Response]
}

to register a new user. Class Tenant is a simple case class:

case class Tenant(val vstNumber:String, val divisionNumber:String) 

Trait AServiceMock mimics the registration logic by using a mocked version of AService

trait AServiceMock {
  def registrationService = {
    val service = mock[AService]
    service.registerNewUser(anyString) returns Future(fixedResponse)
    service
  }
}

Iow whenever registerNewUser is called on AService the response will be "fixedResponse" (defined elsewhere).

My question is, how do I define the implicit tenant-parameter as a mockito matcher like anyString?

btw. I'm using Mockito with Specs2 (and Play2)

Intercurrent answered 26/5, 2015 at 7:35 Comment(2)
A wild guess: what about implicit def tenantMatcher = any[Tenant]?Selfappointed
@Selfappointed perfect guess! It took me two cup of coffees see below ;)Intercurrent
I
21

Sometimes you have to post on SO first to come up with the completely obvious answer (duhh):

service.registerNewUser(anyString)(any[Tenant]) returns Future(fixedResponse)
Intercurrent answered 26/5, 2015 at 8:56 Comment(0)
H
1

This a complement to @simou answer. For now I think this is how it should be done, but I think it is intresting to know why the alternative solution proposed by @Enrik should be avoided as it may fail at run time with a cryptic error in some circumstances.

What you can safely do is if you want an exact match on your implicit argument for your stub, you can just add it in the scope :

trait AServiceMock {
  implicit val expectedTenant: Tenant = Tenant("some expected parameter")
  def registrationService = {
    val service = mock[AService]
    service.registerNewUser(anyString) returns Future(fixedResponse)
    service
  }
}

This will work fine but only if service.registerNewUser is expected to be called with the exact same tenant that the one provided by the implicit value expectedTenant .

What will not reliably work on the other hand is anything in the style :

implicit val expectedTenant1: Tenant = any[Tenant]
implicit def expectedTenant2: Tenant = any[Tenant]
implicit def expectedTenant3: Tenant = eqTo(someTenant)

To reason is related to how mockito create its argument matcher.

When you write myFunction(*,12) returns "abc" mockito actually use a macro that :

  1. add code to intialize a list were argument matcher can register
  2. If needed, wrap all argument that are not matcher in matchers.
  3. add code to retrive the list of matchers that were declared for this function.

In the case of expectedTenant2 or expectedTenant3 what may append is that a first argument matcher will be registerd when the function is evaludated. But the macro will not see this function is registering a macther. It will only consider the declared return type of this function and so may decide to wrap this returned value inside a second matcher.

So in practice if you have code like this

trait AServiceMock {
  implicit def expectedTenant(): Tenant = any[Tenant]
  def registrationService = {
    val service = mock[AService]
    service.registerNewUser(anyString) returns Future(fixedResponse)
    service
  }
}

You expect it to be like that after applying the implicit :

trait AServiceMock {
 
  def registrationService = {
    val service = mock[AService]
    service.registerNewUser(anyString)(any[Tenant]) returns Future(fixedResponse)
    service
  }
}

But actually mockito macro will make it as something more or less like that :

trait AServiceMock {
 
  def registrationService = {
    val service = mock[AService]
    // In practice the macro use DefaultMatcher and not eqTo but that do not change much for the matter we discuss.
    service.registerNewUser(anyString)(eqTo(any[Tenant])) returns Future(fixedResponse)
    service
  }
}

So now you declare two matcher inside the implicit argument of your stub. When mockito will retrive the list of matchers that were declared for registerNewUser, it will see three of them and will think that you are trying to register a stub with three argument for a function that need only two and will log :

Invalid use of argument matchers!
2 matchers expected, 3 recorded:

I'm not yet sure why it may still work in some cases, my hypotheses are :

  • Maybe the macro sometime decide in some case that a matcher is not needed, and do not wrap the value returned by implicit function in an additional matcher.
  • Maybe with some leniency option enabled, mockito ignore additional matcher. Even if that was the case, the additonal matcher may mess up the order of the argument for your stub.
  • It may be also possible that under some circonstance, the scala compiler inline the implicit def, this would allow the macro to see that a matcher was used.
Hoodoo answered 17/5, 2021 at 17:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.