Hot questions for Using Mockito in specs

Question:

I'm new with scala. I'm trying to UT method inside my object Category, using Specs2 for UT and Mockito for mock. Why should I mock this? because this method take some data from mongo. There is my example

object Category extends MongoBase[Category]("categories") {
....
def myMethod(str: String): String
....
}

I've tried to mock object this way:

val mockCategory = mock[Category.type]
mockCategory.myMethod("1") returns "2"

But my test failed

   Cannot mock/spy class 
Mockito cannot mock/spy following:
  - final classes
  - anonymous classes
  - primitive types
org.mockito.exceptions.base.MockitoException: 
Cannot mock/spy class
Mockito cannot mock/spy following:
  - final classes
  - anonymous classes
  - primitive types
    at CategoryMockSpec.mock(CategoryMockSpec.scala:14)
    at CategoryMockSpec$$anonfun$1$$anonfun$apply$1.apply(CategoryMockSpec.scala:18)
    at CategoryMockSpec$$anonfun$1$$anonfun$apply$1.apply(CategoryMockSpec.scala:16)

Thanks for any advice!


Answer:

Try to move all behavior, that you need to test into class or trait level. You'll be able to mixin different implementation in production code and test code.

For example:

trait CategoryApi {
  def importantMethod: Int 
}

class Category extends MongoBase[Category]("categories") with CategoryApi {
  override def importantMethod = { /*go to mongo for real data*/ }
}

class CategoryTest with CategoryApi {
  override def importantMethod = 42
}

// service which uses categories

class SomeService(catApi: CategoryApi) {
  def methodToTest = {
    importantMethod
  }
}

// your test

test() {

val service = new SomeService(CategoryTest())
service.methodToTest == 42 // return 42...


}

Question:

I never expected that I will need to ask a question on this site because everything is already answered normally but with Scalatra... I haven't find a lot of information so here it is:

I'm not experienced with all that so maybe I'm missing something but from what I understand, if I want to test the API that I develop on Scalatra, I need to start the server everytime I run the test suit, right ?

Second question, how can I reset the invocation counter on a method so I don't have to calculate how many times the method has been called since the beginning of the test suite ? Right now using this give me more than one because it counts the previous test.

there was one(*instance*).*method*(*parameter*)

I can still get around the problem by either counting or putting the test as first test for now but it's not a sustainable solution...

Other thing that I found: Reset method on the mock... not found http://docs.mockito.googlecode.com/hg/org/mockito/Mockito.html#17

Isolating the test in a class scope: We need to add

val servlet = new Servlet(eventRepoMock)
addServlet(servlet, "/*")

and we can't repeat the addServlet at every initialization https://etorreborre.github.io/specs2/guide/SPECS2-3.5/org.specs2.guide.Isolation.html

Last thing that I try is:

servlet.repo = mock[EventRepo]

but repo being a value, I can't change it like this.

Neither of these "solutions" feel very clean so I was wondering if someone had a genius idea that can solve that mess !?

Thank you in advance !

EDIT: Thanks to Eric's comment the above question are solve(that was easy) but now I have problem because I'm testing the get/post which are asynchronous call so resetting the mock does not happen at the right time... Any suggestion ?

Here's a simplified version of the code:

class EventServiceSpec extends ScalatraSpec with Mockito with Before { def is = s2"""
Event Service

GET an existing event
must return status 200                              $get_status200
must return the event with id = :id                 $get_rightEventElement
must call findById once on the event repository     $get_findByIdOnRepo
"""

lazy val anEvent = Event(1, "Some Event"
lazy val eventsBaseUrl = "/events"
lazy val existingEventUrl = s"$eventsBaseUrl/${anEvent.id}"

lazy val eventRepoMock = mock[EventRepository]

lazy val servlet = new Servlet(eventRepoMock)
addServlet(servlet, "/*")

def before = {
    eventRepoMock.findById(anEvent.id) returns Option(anEvent)
    eventRepoMock.findById(unexistingId) returns None
    eventRepoMock.save(anEvent) returns Option(anEvent)
}

def get_status200 = get(existingEventUrl){
    status must_== 200
}

def get_findByIdOnRepo = get(existingEventUrl){
    // TODO count considering previous test... need to find a cleaner way
    there was three(eventRepoMock).findById(anEvent.id)
}

Answer:

All org.mockito.Mockito functions can still be used in a specs2 specification and reset is one of them.

Now, since you are sharing the state of a mock object across several examples, you not only need to reset the mock state before each example but you also need to make your specification sequential:

class EventServiceSpec extends ScalatraSpec with Mockito 
  with BeforeAll with BeforeEach { 
  def is = sequential ^ s2"""
  Event Service

  GET an existing event
    must return status 200                              $get_status200
    must return the event with id = :id                 $get_rightEventElement
    must call findById once on the event repository  $get_findByIdOnRepo
  """

  lazy val anEvent = Event(1, "Some Event")
  lazy val eventsBaseUrl = "/events"
  lazy val existingEventUrl = s"$eventsBaseUrl/${anEvent.id}"

  lazy val eventRepoMock = mock[EventRepository]

  lazy val servlet = new Servlet(eventRepoMock)

  def beforeAll = addServlet(servlet, "/*")

  def before = {
    reset(eventRepoMock)
    eventRepoMock.findById(anEvent.id) returns Option(anEvent)
    eventRepoMock.findById(unexistingId) returns None
    eventRepoMock.save(anEvent) returns Option(anEvent)
  }

  def get_status200 = get(existingEventUrl){
    status must_== 200
  }

  def get_findByIdOnRepo = get(existingEventUrl){
   there was one(eventRepoMock).findById(anEvent.id)
  }
}

Question:

I have a trait that I want to mock and use that mocked Trait in another Service during testing. The problem is, that I receive a Nullpointerexception when I try to mock the return value of the indexDocuments function.

Testmethod:

"createDemand must return None if writing to es fails" in new WithApplication {
  val demandDraft = DemandDraft(UserId("1"), "socken bekleidung wolle", Location(Longitude(52.468562), Latitude(13.534212)), Distance(30), Price(25.0), Price(77.0))
  val es = mock[ElasticsearchClient]
  val sphere = mock[SphereClient]
  val productTypes = mock[ProductTypes]

  sphere.execute(any[ProductCreateCommand]) returns Future.successful(product)
  productTypes.demand returns ProductTypeBuilder.of("demand", ProductTypeDrafts.demand).build()
  // this line throws the nullpointer exception
  es.indexDocument(any[IndexName], any[TypeName], any[JsValue]) returns Future.failed(new RuntimeException("test exception"))

  val demandService = new DemandService(es, sphere, productTypes)
  demandService.createDemand(demandDraft) must be (Option.empty[Demand]).await
}

Trait:

sealed trait ElasticsearchClient {
  implicit def convertListenableActionFutureToScalaFuture[T](x: ListenableActionFuture[T]): Future[T] = {
    val p = Promise[T]()
    x.addListener(new ActionListener[T] {
      def onFailure(e: Throwable) = p.failure(e)
      def onResponse(response: T) = p.success(response)
    })
    p.future
  }

  lazy val client = createElasticsearchClient()
  def close(): Unit
  def createElasticsearchClient(): Client

  def indexDocument(esIndex: IndexName, esType: TypeName, doc: JsValue): Future[IndexResponse] =
    client.prepareIndex(esIndex.value, esType.value).setSource(doc.toString()).execute()
  def search(esIndex: IndexName, esType: TypeName, query: QueryBuilder): Future[SearchResponse] =
    client.prepareSearch(esIndex.value).setTypes(esType.value).setQuery(query).execute()
}

Exception

[error]    NullPointerException:   (DemandServiceSpec.scala:89)
[error] services.DemandServiceSpec$$anonfun$1$$anonfun$apply$8$$anon$2$$anonfun$8.apply(DemandServiceSpec.scala:89)
[error] services.DemandServiceSpec$$anonfun$1$$anonfun$apply$8$$anon$2$$anonfun$8.apply(DemandServiceSpec.scala:89)
[error] services.DemandServiceSpec$$anonfun$1$$anonfun$apply$8$$anon$2.delayedEndpoint$services$DemandServiceSpec$$anonfun$1$$anonfun$apply$8$$anon$2$1(DemandServiceSpec.scala:89)
[error] services.DemandServiceSpec$$anonfun$1$$anonfun$apply$8$$anon$2$delayedInit$body.apply(DemandServiceSpec.scala:81)
[error] play.api.test.WithApplication$$anonfun$around$1.apply(Specs.scala:23)
[error] play.api.test.WithApplication$$anonfun$around$1.apply(Specs.scala:23)
[error] play.api.test.PlayRunners$class.running(Helpers.scala:49)
[error] play.api.test.Helpers$.running(Helpers.scala:403)
[error] play.api.test.WithApplication.around(Specs.scala:23)
[error] play.api.test.WithApplication.delayedInit(Specs.scala:20)
[error] services.DemandServiceSpec$$anonfun$1$$anonfun$apply$8$$anon$2.<init>(DemandServiceSpec.scala:81)
[error] services.DemandServiceSpec$$anonfun$1$$anonfun$apply$8.apply(DemandServiceSpec.scala:81)
[error] services.DemandServiceSpec$$anonfun$1$$anonfun$apply$8.apply(DemandServiceSpec.scala:81)

Please let me know if you need additional information.


Answer:

I found out that the any[] Matchers in the indexDocuments call are the problem. When I replace them with the actual values it works:

"createDemand must return None if writing to es fails and deleteDemand should be called once with correct parameters" in new WithApplication {
  val demandDraft = DemandDraft(UserId("1"), "socken bekleidung wolle", Location(Longitude(52.468562), Latitude(13.534212)), Distance(30), Price(25.0), Price(77.0))
  val es = mock[ElasticsearchClient]
  val sphere = mock[SphereClient]
  val productTypes = mock[ProductTypes]

  sphere.execute(any[ProductCreateCommand]) returns Future.successful(product)
  sphere.execute(any[ProductDeleteByIdCommand]) returns Future.successful(product)
  productTypes.demand returns ProductTypeBuilder.of("demand", ProductTypeDrafts.demand).build()
  es.indexDocument(IndexName("demands"), TypeName("demands"), Json.toJson(demand)) returns Future.failed(new RuntimeException("test exception"))

  val demandService = new DemandService(es, sphere, productTypes)
  demandService.createDemand(demandDraft) must be (Option.empty[Demand]).await
}

Question:

While writing tests I'm confronted with the following exception:

java.lang.ClassCastException: codegen.java.lang.Object$MockitoMock$641592186 cannot be cast to cats.effect.IO (MyRepositorySpec.scala:19)

Which occurs when running this test code with specs2:

class MyRepositorySpec extends Specification with Mockito with TestData {
    ...
    val m = mock[MyDAO[IO]].smart
    m.createTable returns IO { Right[Throwable, Int](1) } // <- this is line 19

    val r = new MyRepository[IO](m)
    r.setup.unsafeRunSync() must beNone
    ...
}

MyDAO looks like this:

class MyDAO[M[_] : Monad](val transactor: Transactor[M])(implicit val AE: ApplicativeError[M, Throwable]) extends DataAccessObject[M, MyObject]

and the DataAccessObject like this:

trait DataAccessObject[M[_], T <: Entity]

I'm at a loss how to fix/correctly implement this. Any help would be appreciated. Thank you!


Answer:

Try

class IOMyDAO(override val transactor: Transactor[IO]) extends MyDAO[IO](transactor)

val m = mock[IOMyDAO].smart

Based on this answer.


You should try to use org.mockito.Mockito#when instead of specs2s internal metod matching functionality:

when(m.createTable) thenReturn IO { Right[Throwable, Int](1) }

instead of

m.createTable returns IO { Right[Throwable, Int](1) }

Question:

Here is the code I am trying to run:

import org.specs2.mock.Mockito
import org.specs2.mutable.Specification
import org.specs2.specification.Scope
import akka.event.LoggingAdapter

class MySpec extends Specification with Mockito {


  "Something" should {
      "do something" in new Scope {

      val logger = mock[LoggingAdapter]

      val myVar = new MyClassTakingLogger(logger)

      myVar.doSth()

      there was no(logger).error(any[Exception], "my err msg")
      }
  }

}

When running this, I get the following error:

[error]    org.mockito.exceptions.misusing.InvalidUseOfMatchersException:
[error]    Invalid use of argument matchers!
[error]    2 matchers expected, 1 recorded:
[error]    -> at         org.specs2.mock.mockito.MockitoMatchers$class.any(MockitoMatchers.scala:47)
[error]
[error]    This exception may occur if matchers are combined with raw values:
[error]        //incorrect:
[error]        someMethod(anyObject(), "raw String");
[error]    When using matchers, all arguments have to be provided by matchers.
[error]    For example:
[error]        //correct:
[error]        someMethod(anyObject(), eq("String by matcher"));

Which would make a lot of sense, but neither eq("my err msg") nor equals("my err msg") does the job as I get an error. What am I missing?


Answer:

I would like to add that you should be wary of default arguments, i.e. if using matchers when stubbing methods, make sure to pass argument matchers for all arguments, because default arguments will almost certainly have constant values - causing this same error to appear.

E.g. to stub the method

def myMethod(arg1: String, arg2: String arg3: String = "default"): String

you cannot simply do

def myMethod(anyString, anyString) returns "some value"

but you also need to pass an argument matcher for the default value, like so:

def myMethod(anyString, anyString, anyString) returns "some value"

Just lost half an hour figuring this out :)

Question:

I am using specs2 as my test framework. I want to simulate a use case in which actionA return a failing future. Like this:

  val actionA = mock[ActionA]
  val actionB = new ActionB(actionA)
  actionA.doSomthing(x) returns Future.failed(new Exception("bla"))
  try {
    Await.result(actionB.doSomthing(request), 1 seconds)
  }catch {
    case e: Exception => println("exception caught: " + e);
  }

The problem is that my test exit with this exception if I am not catching it, doesn't specs2 have a nicer way to swallow exceptions? Is there a better way to test such scenario?

Thanks!


Answer:

Await.result(actionB.doSomething(request), 1 seconds) must throwA[Exception]

via https://etorreborre.github.io/specs2/guide/SPECS2-3.6.5/org.specs2.guide.Matchers.html -> Exception

Question:

I have a mocked service which takes 3 parameters. How do I access second argument?

mockedService.invoke(arg1, arg2, arg3) answers {
  (params, mock) => {
     //Do something with params.arg2 to the value that is returned from the invocation
  }
}

In the documentation, they states "the array of the method parameters will be passed" how do I access the second argument (arg2 in this case)? Do I cast params with List[Any]?

Thanks


Answer:

You need to match the parameters to an Array like this:

import org.specs2.Specification
import org.specs2.mock.Mockito

class TestSpec extends Specification with Mockito { def is = s2"""
  test $e1
"""

  def e1 = {
    val mockedService = mock[Service]
    mockedService.invoke(1, 2, 3).answers { (params, mock) =>
      params match {
        case Array(a, b: Int, c) => b + 2
      }
    }
    mockedService.invoke(1, 2, 3) must_== 4
  }
}

trait Service {
  def invoke(arg1: Int, arg2: Int, arg3: Int) = 1
}

Question:

I have a service method as following:

override def update(group: JourneyGroup, name: String, operator: User): Future[Either[String, JourneyGroup]] = {
    for {
      updatedCount <- journeyGroupDao.update(group.copy(name = name), operator.id.get)
      updatedGroup <- journeyGroupDao.findOneById(group.id.get)
    } yield
      (updatedCount, updatedGroup) match {

        case (1, Some(g)) =>
          system.eventStream.publish(JourneyGroupUpdated(g, group))
          Right(g)

        case (1, None) => Left(s"failed to find updated journey group object - ${group.id.get}")

        case _ => Left(s"failed to update journey group object - ${group.id.get}")

      }
  }

And unit test for it look like this:

val existingGroup = mock[JourneyGroup]
      existingGroup.id returns Some(123)

      val updatedGroup = mock[JourneyGroup]

      val operator = mock[User]
      operator.id returns Some(876)

      doReturn(Future.successful(1)).when(journeyGroupDao).update(Matchers.any[JourneyGroup], Matchers.eq(876))
      doReturn(Future.successful(updatedGroup)).when(journeyGroupDao).findOneById(123, includeDeleted = false)
      doNothing.when(eventStream).publish(Matchers.any[JourneyGroupUpdated])

      val future = journeyGroupService.update(existingGroup, "new name", operator)
      Await.result(future, Duration.Inf) must beRight{ g: JourneyGroup =>

        g must_=== updatedGroup

      }
      there was one(eventStream).publish(Matchers.any[JourneyGroupUpdated])

The method works perfectly fine, in regular execution. However, when I run the test I am getting casting error:

[error]    java.lang.ClassCastException: model.JourneyGroup$$EnhancerByMockitoWithCGLIB$$a9b16db0 cannot be cast to scala.Option (JourneyGroupService.scala:101)
[error] services.JourneyGroupServiceImpl$$anonfun$update$1$$anonfun$apply$1.apply(JourneyGroupService.scala:101)

I am not even sure where to start with it. I would appreciate for any idea.


Answer:

In the code of the for-comprehension says that the return type of:

updatedGroup <- journeyGroupDao.findOneById(group.id.get)

is Option[JourneyGroup], but in the declaration of the mock interactions, an JourneyGroup is given:

val updatedGroup = mock[JourneyGroup]
...
doReturn(Future.successful(updatedGroup)).when(journeyGroupDao).findOneById(123, includeDeleted = false) 

updatedGroup must be of type Option[JourneyGroup]

That said, I do not recommend the use of mocks in Scala tests. Using traits and minimal implementations will let the compiler point at those errors. Mocks move those checks to the runtime and obfuscate the actual cause, like in this case.

Question:

In specs2, we can mock a method and let it throw exception:

class Hello {
   def say():Unit = println("Hello, world")
}

val hello = mock[Hello]
hello.say() throws new RuntimeException("something wrong")

But how to make it just throw the first time, and then always do nothing?


Answer:

This is actually a mockito question, not a specs2 one. From the mockito documentation:

when(mock.someMethod("some arg"))
   .thenThrow(new RuntimeException())
   .thenReturn("foo");

Alternative, shorter version of consecutive stubbing:

 when(mock.someMethod("some arg"))
   .thenReturn("one", "two", "three");