Search code examples
scalamockitoguicescalatest

Scala Mockito Guice and Partial Mocking .... methods get called twice


I have the following code written in Scala, Guice, Mockito and ScalaTest

import javax.inject.Singleton
import com.google.inject.Inject
@Singleton
class TestPartialMock @Inject()(t1: Test1, t2: Test2) {
   def test3() = "I do test3"
   def workHorse() : List[String] = {
      println("+++++ came inside ++++++++")
      List(t1.test1(), t2.test2(), test3())
   }
}


class MainModule extends ScalaModule {
   override def configure() = {
      bind[Test1]
      bind[Test2]
      bind[TestPartialMock]
   }
}

and I have written unit test cases with partial mocking

class PartialMockTest extends FunSpec with Matchers {
   describe("we are testing workhorse but mock test3") {
      it("should return mock for test3") {
         val module = new TestModule
         val injector = Guice.createInjector(module)
         val tpm = injector.getInstance(classOf[TestPartialMock])
         val result = tpm.workHorse()
         result should contain ("i do test2")
         result should contain ("i do test1")
         result should contain ("I am mocked")
         result should not contain ("I do test3")
      }
   }
}

class TestModule extends AbstractModule with ScalaModule with MockitoSugar {
   override def configure() = {
      val module = new MainModule()
      val injector = Guice.createInjector(module)
      val realobject = injector.getInstance(classOf[TestPartialMock])
      val x = spy(realobject)
      when(x.test3()).thenReturn("I am mocked")
      when(x.workHorse()).thenCallRealMethod()
      bind(classOf[TestPartialMock]).toInstance(x)
   }
}

My tests are successful and I can see that it mocks the right set of methods and calls the actual implementation of the right set of methods. BUT when I look at the output I see

info] Compiling 5 Scala sources to /Users/IdeaProjects/GuicePartialMock/target/scala-2.12/classes...
[info] Compiling 1 Scala source to /Users/IdeaProjects/GuicePartialMock/target/scala-2.12/test-classes...
+++++ came inside ++++++++
+++++ came inside ++++++++
[info] PartialMockTest:
[info] we are testing workhorse but mock test3
[info] - should return mock for test3
[info] Run completed in 2 seconds, 92 milliseconds.

Why am I seeing the print statement came inside twice?

Edit::

Based on advice of Tavian ... this is the final code which worked

class TestModule extends AbstractModule with ScalaModule with MockitoSugar {
   override def configure() = {
      val module = new MainModule()
      val injector = Guice.createInjector(module)
      val realobject = injector.getInstance(classOf[TestPartialMock])
      val x = spy(realobject)
      when(x.test3()).thenReturn("I am mocked")
      bind(classOf[TestPartialMock]).toInstance(x)
   }
}

Solution

  • For spies, you need to be careful of expressions like

    when(x.workHorse()).thenCallRealMethod()
    

    because x.workHorse() really is invoked in this expression! x doesn't "know" that it's inside a when() call, as the expression is lowered into something like this:

    tmp1 = x.workHorse();
    tmp2 = when(tmp1);
    tmp3 = tmp2.thenCallRealMethod();
    

    Instead, you can write

    doCallRealMethod().when(x).workHorse()
    

    which will suppress the invocation of the real workHorse() implementation.

    But, you don't need to do any of this for this example—calling real methods is the default behaviour of spies.