Home > OS >  Mock a trait method with Mockito in scala
Mock a trait method with Mockito in scala

Time:12-18

I am trying to mock a method that it is calling an external service, I simplified the problem to try understand the root problem, I see I can mock a method in a trait but I cannot use same method in other class that is extending the Trait. Why? and how can I fix the problem?

I want to mock the login method:

case class User(id: Int, name: String)

trait LoginService {
  def login(name: String, password: String): Option[User] = Some(User(1, "anne"))
}

class OtherService extends LoginService {
  def getId: Int = {
     val getPassword="xx" // more stuff to test...
     login("Anne", getPassword).get.id
  }
}

If I am trying to mock it in my scala test mockito:

  class MySpec extends AnyWordSpec with Matchers with MockitoSugar {

  "test3" in {
    val service = mock[LoginService]
    when(service.login("Anne", "xx")) thenReturn Some(User(222, "AA"))

    val other = new OtherService()
    other.getId should be(222)
  }

  "test2" in {
    val service = mock[OtherService]
    when(service.login("Anne", "xx")) thenReturn Some(User(222, "AA"))

    val other=new OtherService()
    other.getId should be(222)
  }

  "test1" in {
    val service = mock[LoginService]
    when(service.login("Anne", "xx")) thenReturn Some(User(222, "AA"))

    service.login("Anne","xx").get.id should be(222)
  }
}

Only test1 is working, but If I need to validate the method getId it fails. Why test2 and test3 are not simulating my mocked user with id 222??

Dependencies:

     "org.scalatest" %% "scalatest" % "3.2.14"  % Test,
     "org.mockito"  %% "mockito-scala-scalatest" % "1.17.12" % Test,

CodePudding user response:

In your "test3", you mock a trait. This creates an anonymous instance of the trait with the mocked behaviour but the instance of OtherService you create after has no relation with it.

In your "test2", you mock the OtherService which creates an instance of it but you then create another instance for your assertion. The instance you assert on has no locked behaviour.

In your "test1", it works because you assert on the instance that is mocked. However it's an instance of the trait and you would like an instance of the extending class.


What you want is almost what you wrote in "test2" but you need to use the same instance for mocking and for asserting.

You could write the following:

val service = mock[OtherService]
when(service.login("Anne", "xx")) thenReturn Some(User(222, "AA"))

service.getId should be(222)

But then you'd have an issue because by default all methods of a mocked instance are mocked and getId would be mocked as well.

See Use Mockito to mock some methods but not others for how to define "partial mocks".


That being said, your design is actually flawed, you should rather use composition than inheritance:

class OtherService(loginService: LoginService)

And in your tests:

val fakeService = mock[LoginService]
when(fakeService.login("Anne", "xx")) thenReturn Some(User(222, "AA"))

val other = new OtherService(fakeService)
other.getId should be(222)
  • Related