Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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)

like image 366
simou Avatar asked May 26 '15 07:05

simou


People also ask

Can the Mockito Matcher methods be used as return values?

Matcher methods can't be used as return values; there is no way to phrase thenReturn(anyInt()) or thenReturn(any(Foo. class)) in Mockito, for instance. Mockito needs to know exactly which instance to return in stubbing calls, and will not choose an arbitrary return value for you.

What can I use instead of Mockito matchers?

Since Mockito any(Class) and anyInt family matchers perform a type check, thus they won't match null arguments. Instead use the isNull matcher.

What is argument matcher in Mockito?

Argument matchers are mainly used for performing flexible verification and stubbing in Mockito. It extends ArgumentMatchers class to access all the matcher functions. Mockito uses equal() as a legacy method for verification and matching of argument values.


2 Answers

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)
like image 121
simou Avatar answered Oct 19 '22 22:10

simou


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.
like image 37
Thibault Urien Avatar answered Oct 19 '22 22:10

Thibault Urien