Mock case class in scala

Viewed 58

I have a trait

 trait Writer[T <: Attribute] {
  def read(boundingBox: BoundingBox): Future[List[T]]
}

and a case class

case class WriterImpl[T <: Attribute]()(implicit manifest: Manifest[T]) extends Writer[T] with LazyLogging {

override def read(boundingBox: BoundingBox): Future[List[T]] = {
}
..
}

In the Test class I want to mock the method read()

I tried this with exact values

  val writer = mock[WriterImpl[Attrib]]
        when(writer.read(new BoundingBox(41.90178412, 41.8798685, -87.62687021, -87.64884287)))
          .thenReturn(Future.successful(scala.collection.immutable.List(list)))

 verify(writer).read(new BoundingBox(41.90178412, 41.8798685, -87.62687021, -87.64884287))

also tried

val writer = mock[WriterImpl[Attrib]]
            when(writer.read(ArgumentMatchers.any()))
              .thenReturn(Future.successful(scala.collection.immutable.List(list)))
    
     verify(writer).read(ArgumentMatchers.any())

Instead of calling the mock method it calls the actual method

1 Answers

In the first example, the problem is you use 2 different BoundingBox objects as parameters for the read method: one for when and another one for the verify. They will not match because at runtime they are different instances of the same BoundingBox class. You should use the same object. Store it before the when clause and then call verify passing that same object.

I assume you just removed the obvious call to writer.read(...) anywhere between the when and verify clauses. verify expects at least one call to read, otherwise it throws a "Wanted but not invoked: writerImpl.read(...); Actually, there were zero interactions with this mock.."

The second example should work, but you will still have the same issue if you'll try to check the result. You will result in 2 different instances of a Future: one in the when and another one in the verify. Since they are 2 different objects at runtime, they will not match:

result shouldBe Future.successful(scala.collection.immutable.List(new Attribute))

The output confirms the runtime sees 2 different instances:

Expected :Future(Success(List(org.scala.snippets.Test$Attribute@373052b5)))
Actual   :Future(Success(List(org.scala.snippets.Test$Attribute@17207f5b)))

Again, just store the Future before when and pass the same Future to both when and shouldBe. With these adjustments this should work. Checking the result of read confirms the same Future was returned from the call:

import Test.{Attribute, BoundingBox, WriterImpl}

import org.mockito.MockitoSugar
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec

import scala.concurrent.Future

class ProgramTest extends AnyWordSpec with MockitoSugar with Matchers {
  "Program " should {
    "return the mocked succf " in {
      val succf  = Future.successful(scala.collection.immutable.List(new Attribute))
      val bb     = new BoundingBox(41.90178412, 41.8798685, -87.62687021, -87.64884287)
      val writer = mock[WriterImpl[Attribute]]
      when(writer.read(bb)).thenReturn(succf)
    
      val result = writer.read(bb)
    
      verify(writer, times(1)).read(bb)
      result shouldBe succf
    }
  }
}

Here's my build.sbt in case you need it:

libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.12" % Test
libraryDependencies += "org.scalatestplus" %% "scalacheck-1-16" % "3.2.12.0" % Test
libraryDependencies += "org.scalatestplus" %% "mockito-4-5" % "3.2.12.0" % Test
libraryDependencies += "org.mockito" % "mockito-core" % "4.6.1" % Test
libraryDependencies += "org.mockito" %% "mockito-scala" % "1.17.7" % Test
Related