Search code examples
scalaunit-testingmockitoscalatestscalamock

How to mock a method whose parameter is a new instance in scala


I have a method in a class:

def delete(Token, Client, Scope): Future[Int]

and this method is called elsewhere in another class inside another method as:

acr.delete(Token(token), client, scope)

where token is a String and client and scope are types of Client and Scope respectively:

case class Client(client:   String) extends AnyVal
case class Scope(scope:     String) extends AnyVal

When I try to mock the delete method in my test, I do it as :

when(mockService
            .delete(
              token      = any[Token],
              service    = any[Client],
              scope      = any[Scope]
            )
        ).thenReturn(1.toFut)

which yields a Matching Exception which causes a Null Pointer Exception:

Method threw 'org.mockito.exceptions.misusing.InvalidUseOfMatchersException' exception. Cannot evaluate repositories.common.Service$MockitoMock$1804616202.toString()

mockService is a mock[Service]. I have another method mocked, belonging to Service and that mock does not throw any errors

When I debug it line-by-line, the code fails on the token = any[Token] line. I'm not sure how else I can use Matchers and construct a mock.

What do you suggest I do?


Solution

  • I assume your code looks the same as:

    case class Token(client:   String) extends AnyVal
    case class Client(client:   String) extends AnyVal
    case class Scope(scope:     String) extends AnyVal
    
    class Service(implicit val ec: ExecutionContext) {
      def delete(token: Token, client: Client, scope: Scope): Future[Int] = {
        Future(1)
      }
    }
    

    and for this code, you can create Token as Token[any[String]]

    import org.mockito.ArgumentMatchers.any
    import org.mockito.Mockito.when
    import org.mockito.MockitoSugar.mock
    import scala.concurrent.{ExecutionContext, Future}
    
    implicit val ec: ExecutionContext = ExecutionContext.global
    val mockService: Service = mock[Service]
    when(
      mockService.delete(
        token      = Token(any[String]),
        client    = Client(any[String]),
        scope      = Scope(any[String])
      )
    ).thenReturn(Future(2))
    

    this code works and doesn't throw NPE.

    I have assumption: your code throws NPE because case classes extends AnyVal. Let's look at AnyVal source code:

    abstract class AnyVal extends Any {
      def getClass(): Class[_ <: AnyVal] = null
    }
    

    it has getClass which returns null - that sounds not null-safety. If you will remove extends AnyVal from your case classes, your code will work. Probably any matcher call getClass inside itself, so it's a usual things for testing libraries.