21

I am trying to mock a Scala singleton object. In particular, I need to mock the object play.api.libs.ws.WS used inside a service component (class under test). Using Mockito this is not possible, the test execution fails in the following way:

[error]    MockitoException: : 
[error] Cannot mock/spy class play.api.libs.ws.WS$
[error] Mockito cannot mock/spy following:
[error]   - final classes
[error]   - anonymous classes
[error]   - primitive types  (GeolocationSpec.scala:18)

Reading here, it seems that Scalamock allows to do it:

To mock a standalone singleton object, use org.scalamock.annotation.mockObject.

My service component is something like this:

trait GeolocationService {
  def wsClient = WS
  def getPath(origin: Location, destination: Location): Future[Route]
}

class DefaultGeolocationService extends GeolocationService {

  val serviceProviderEndpoint = Play.current.configuration.getString("api.directions.endpoint")

  override def getPath(origin: Location, destination: Location): Future[Route] = {

    val params = Seq(
      "origin" -> s"${origin.lat},${origin.lon}",
      "destination" -> s"${destination.lat},${destination.lon}"
    );
    val resp = wsClient.url(serviceProviderEndpoint.get).withQueryString(params: _*).get()
    resp.map {
      // omitted code
    }
  }
}

My build.sbt has all these dependencies:

[...]
"org.scalatest" %% "scalatest" % "2.2.1",
"org.specs2" %% "specs2" % "2.3.13" % "test",
"org.scalamock" %% "scalamock-specs2-support" % "3.0.1" % "test",
"org.scalamock" %% "scalamock-scalatest-support" % "3.0.1" % "test",
"org.scalamock" %% "scalamock" % "3.0.1",
[...]

but I cannot find this: org.scalamock.annotation.mockObject

Probably this can be done also using EasyMock and PowerMock, but I cannot find any Scala example code.

Any idea?

user2664655
  • 251
  • 1
  • 3
  • 9
  • 1
    Mock the interface it implements instead. Take that interface as a dependency where it is used. –  Aug 13 '14 at 19:56
  • How are you calling it? Like `mock [WS.type]` or another way? – wheaties Aug 13 '14 at 22:13
  • 2
    I second @PolymorphicPotato. It is unfortunate that Play's WS is an object instead of an implementation of a generic trait. If you can add a short snippet of your code using WS we can guide you to decouple it from WS and make it easier to mock. – vptheron Aug 13 '14 at 22:14
  • @wheaties yes, the only way I found using Mockito to mock an object without compilation errors is `mock[WS.type]`, but it's not working. – user2664655 Aug 14 '14 at 00:09
  • @PolymorphicPotato Can you tell me exactly what you mean? Which interface would you mock in the case of WS singleton object? – user2664655 Aug 14 '14 at 00:10
  • @vptheron Added the code that is using WS. – user2664655 Aug 14 '14 at 09:25
  • I didn't understand your question. Do you want to mock a webservice call of your code in your tests? – dirceusemighini Aug 14 '14 at 12:09
  • @dirceusemighini Yes, I want to mock the object `wsClient` inside `GeolocationService` returning a mock of `WSRequestHolder` class when the `url(url: String)` method is called. The problem is that wsClient is a singleton object that I can't refactor, it's provided from Play framework. – user2664655 Aug 14 '14 at 12:25

4 Answers4

13

Mocking singleton objects using ScalaMock 3 is not possible, however Paul Butcher expects to reintroduce this feature in ScalaMock 4 (see http://paulbutcher.com/2014/04/15/scalamock-status-report/)

n_l
  • 844
  • 1
  • 6
  • 19
3

Don't mock the singleton. Instead of WS, make your service component depend on a thin facade hiding it:

trait GeolocationService {
  def ws: (String, Seq[String]) => Promise[Response] = { (url, params) =>
    wsClient.url(serviceProviderEndpoint.get).withQueryString(params: _*).get()
  }
  def getPath(origin: Location, destination: Location): Future[Route]
}

and in your test, just override ws method with a mock, which is now easy to create:

val mockedWs = mock[(String, Seq[String]) => Promise[Response]]
// TODO specify mock's behavior here
val service = new DefaultGeolocationService() {
  override def ws = mockedWs
}
Przemek Pokrywka
  • 2,219
  • 17
  • 23
0

Why you don't use the cake pattern?

It's a little verbose but it will solve your mocking problem.

trait GeolocationServiceComponent {
   val geolocationService:GeolocationService
   trait GeolocationService {      
      def wsClient
      def getPath(origin:Location, destination: Location): Future[Route]   
   }
}

trait GeolocationServiceComponentImpl {
   val geolocationService = new GeolocationService {
      override def wsClient = WS
   }
}


class DefaultGeolocationService extends GeolocationServiceComponent ...

//Defined into your test class
trait MockGeolocationServiceComponent {
       val geolocationService = Mock[GeolocationService]
//Here you define your mock logic       
}

You can also use monads to do this, but I have never implemented, it's described here: Scala dependency injection: alternatives to implicit parameters

Luke Francl
  • 31,028
  • 18
  • 69
  • 91
dirceusemighini
  • 1,344
  • 2
  • 16
  • 35
  • I am not sure to understand what you say. GeolocationService is the class under test and I don't want to mock it. I want to mock wcClient. – user2664655 Aug 14 '14 at 18:10
  • Actually you cant mock an object, it will need to wait for scala 2.12, or complete macron implementation to be able to mock objects. I tried to show you, using cake pattern, a way for you to mock the result of your method that uses WS. Latter I'll change my answer, but basically you can extends your geolocation overwriting the only the method wsClient in the mock] – dirceusemighini Aug 14 '14 at 19:17
0

I don't like to change the design to accommodate this kind of limitation. basically what I can get is all to change the design and then use the mock framework

  //since mocking frameworks on SCALA is not support mocking singleton
  //hacks are either required to change the design or ...
  //I'd rather putting the mocking logic here by myself
  var testUser:Option[User] = None;

  def getCurrentUser(request: Request[Object]) = {
    if( testUser != None ){
    testUser;
  }

hope it helps.

zinking
  • 5,561
  • 5
  • 49
  • 81