Shurik Agulyansky Shurik Agulyansky - 1 month ago 10
Scala Question

Facing some weird casting exceptions only in specs2 unit tests (Scala)

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.