12

I'm having a bit of a tough time trying to understand how to write tests in Scala when implicit parameters are involved.

I have the following (short version) of my code and test:

Implementation (Scala 2.10, Spray and Akka):

import spray.httpx.SprayJsonSupport._
import com.acme.ResultJsonFormat._

case class PerRequestIndexingActor(ctx: RequestContext) extends Actor with ActorLogging {
  def receive = LoggingReceive {
    case AddToIndexRequestCompleted(result) =>
      ctx.complete(result)
      context.stop(self)
  }
}


object ResultJsonFormat extends DefaultJsonProtocol {
  implicit val resultFormat = jsonFormat2(Result)
}

case class Result(code: Int, message: String)

Test (Using ScalaTest and Mockito):

"Per Request Indexing Actor" should {
    "send the HTTP Response when AddToIndexRequestCompleted message is received" in {
      val request = mock[RequestContext]
      val result = mock[Result]

      val perRequestIndexingActor = TestActorRef(Props(new PerRequestIndexingActor(request)))
      perRequestIndexingActor ! AddToIndexRequestCompleted(result)

      verify(request).complete(result)
    }
  }

This line, verify(request).complete(result) uses an implicit Marshaller to turn Result into JSON.

I can bring a marshaller in to scope by adding implicit val marshaller: Marshaller[Result] = mock[Marshaller[Result]] but when I run the test a different instance of Marshaller is used, so the verification fails.

Even explicitly passing the mock Marshaller to complete fails.

So, can any one advise how to create a mock object for an implicit parameter and make sure that instance is the one used?

C0deAttack
  • 24,419
  • 18
  • 73
  • 81
  • How does explicitly passing the mock fail? Why would you want to use a mock marshaller in the first place? (I haven't used mockito so please excuse me if those are stupid questions) – jrudolph May 12 '13 at 17:01

1 Answers1

5

This is a perfect situation to use a Matcher from Mockito for the marshaller arg. You should not need to mock out the implicit marshaller. All you really want to do is verify that complete was called with a result matching what you expected and also some instance of the marshaller. First, if you haven't already done it, bring the Mockito matchers into scope with an import like so:

import org.mockito.Matchers._

Then, if you wanted reference matching on the result, you could verify like so:

verify(request).complete(same(result))(any[classOf[Marshaller[Result]]])

Or, if you wanted equals matching on result you could do:

verify(request).complete(eq(result))(any(classOf[Marshaller[Result]]))

The trick with matchers is that once you use one for one arg, you have to use them for all args, so that's why we have to use one for result too.

cmbaxter
  • 35,283
  • 4
  • 86
  • 95
  • I had tried that too. Though I had not tried using the `same()` matcher. I'm not sure why (yet) the test passes when using `verify(request).complete(same(result))(any[classOf[Marshaller[Result]]])` but does not pass when using `verify(request).complete(eq(result))(any(classOf[Marshaller[Result]]))`. Thanks for your suggestion though, it helped :) – C0deAttack May 13 '13 at 08:59
  • Because the request variable is itself a mock, and the eq matcher uses the equals method which has not been stubbed, you always get false thus no match. Same is a good fit here. If request was not a mock and instead some case class, eq might fit better – cmbaxter May 13 '13 at 09:19
  • 1
    Thanks for the follow up. Apologies for not being clear. Using `eq` the test doesn't even compile. When I use `eq` the error message is "Type mismatch, expected: Marshaller[Boolean], actual: Marshaller[Result]". And it's an error on `any(classOf[Marshaller[Result]])`. I'm not 100% clear on why the error occurs? It's pretty odd. – C0deAttack May 13 '13 at 10:03