Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to mock a method with functional arguments in Scala?

Tags:

mocking

scala

I'm trying to mock a method call that takes a call-by-name argument:

import org.scalatest.WordSpec
import org.scalatest.mock.MockitoSugar
import org.mockito.Mockito._
import org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner

trait Collaborator {
   def doSomething(t: => Thing)
}

trait Thing

@RunWith(classOf[JUnitRunner])
class Test extends WordSpec with MockitoSugar {
   "The subject under test" should {
      "call the collaborator" in {
         // setup
         val m = mock[Collaborator]
         val t = mock[Thing]

         // test code: this would actually be invoked by the SUT
         m.doSomething(t)

         // verify the call
         verify(m).doSomething(t)
      }
   }
}

I'm primarily interested in Mockito since that's what I'm using, but I'd be interested to see whether any of the major mock frameworks is capable of this kind of testing. The Test fails at runtime on the verify line, with an error like

Argument(s) are different! Wanted:  
collaborator.doSomething(  
   ($anonfun$apply$3) <function>  
);  
-> at Test$$anonfun$1$$anonfun$apply$1.apply(Test.scala:27)  
Actual invocation has different arguments:  
collaborator.doSomething(  
    ($anonfun$apply$2) <function>  
);  
-> at Test$$anonfun$1$$anonfun$apply$1.apply(Test.scala:24)  

If I'm understanding the situation correctly, the compiler is implicitly wrapping t in a nullary function that returns t. The mock framework is then comparing that function to the one produced in the test code, which is equivalent but not equals().

My case is a relatively simple version of the problem, but I think this would be an issue with any higher-order function.

like image 806
Aaron Novstrup Avatar asked Jan 28 '10 03:01

Aaron Novstrup


People also ask

What is stub in Scala?

A stub object that supports the record-then-verify style is created with stub . For example: val heaterStub = stub[Heater] Return values that are used by the system under test can be set up by using when before running the tested system.

What is MockitoSugar?

MockitoSugar trait provides some basic syntax sugar for Mockito. Using the Mockito API directly, you create a mock with: val mockCollaborator = mock(classOf[Collaborator]) Using this trait, you can shorten that to: val mockCollaborator = mock[Collaborator]

Can you mock an object in Scala?

Using ScalaMock. ScalaMock is a native, open-source Scala mocking framework written by Paul Butcher that allows you to mock objects and functions. ScalaMock supports three different mocking styles: Function mocks.

How do you mock the same class method?

Mocking is done when you invoke methods of a class that has external communication like database calls or rest calls. Through mocking you can explicitly define the return value of methods without actually executing the steps of the method.


3 Answers

This looks ugly, but hopefully it can help you to find good solution:

import org.scalatest.mock.MockitoSugar import org.mockito.Mockito._  trait Collaborator {    def doSomething(t: => Thing) }  trait Thing  new MockitoSugar {      // setup      val m = mock[Collaborator]      val t = mock[Thing]       m.doSomething(t)       classOf[Collaborator].getMethod("doSomething", classOf[Function0[_]]).invoke(         verify(m),          new Function0[Thing] {              def apply() = null             override def equals(o: Any): Boolean = t == o.asInstanceOf[Function0[Thing]].apply()        }) } 
like image 129
Alexey Avatar answered Oct 25 '22 04:10

Alexey


You can try specs2. In specs2, we "hijack" the Mockito Invocation class to account for byname parameters:

trait ByName { def call(i: =>Int) = i } val byname = mock[ByName]  byname.call(10) there was one(byname).call(10) 
like image 26
Eric Avatar answered Oct 25 '22 04:10

Eric


This problem seems to be specific to by-name invocations because in regular higher order functions you can match against the explicit FunctionX object:

verify(collaborator).somethingElse(any(Function2[String, Thing]))

in the by-name case the wrapping of the argument into a Function0 is done implicitly, and Alexey's answer shows how to invoke the mock with an explicit parameter.

You could write something akin to your own verify which would apply arguments captured by mockito.

Mockito internally records invocation and their arguments with e.g.: http://code.google.com/p/mockito/source/browse/trunk/src/org/mockito/internal/matchers/CapturingMatcher.java

like image 29
miaubiz Avatar answered Oct 25 '22 05:10

miaubiz