Search code examples
scalaplayframeworkspecs2

Specs 2 - I want to mock one of the injected modules into the controller class


Let's say I have a class which has 10 auto injected items:

@Singleton
class ExampleClass @Inject() (
  implicit
  configuration: Configuration,
  secondModule: SecondModule,
  thirdModule: ThirdModule,
  fourthModule: FourthModule,
  ...
) {...}

In my spec2 Instantiation, I want to mock one module like this:

val mockedThirdModule = Mockito.mock(classOf[ThirdModule])

when(mockedThirdModule.foo).thenReturn(Future.successfull("ABC"))

// Now here's my class:

implicit lazy val exampleClass = app.injector.instanceOf[ExampleClass]

// and then something like this:
implicit lazy val copyExampleClass = exampleClass.copy(thirdModule = mockedThirdModule)

Is it possible to do so? Note that I want to keep all other modules untouched and update only thirdModule.

I am putting this in a common trait like this:

trait CommonProps extends PlaySpecification with StubControllerComponentsFactory with BeforeAfterAll {...}

and then extending my specs to this:

@RunWith(classOf[JUnitRunner])
class SomeSpec extends CommonProps with Other Props {...}

Can I inject the variable like this?


Solution

  • You usually want to inject the dependency class(es) (mocked or not) before the main class is constructed.

    This is because it avoids the need to expose the dependency class(es) outside the main class, which would be required to modify the dependency class after main class is constructed. It also avoids the need to make it mutable (which then could lead to surprising results depending when your main class uses the dependency and when you do set the mock).


    That being said, with Play Framework and Guice injection, it's easy to do in tests.

    I see you somehow have access to an app instance in your code. This means you should also have a way to override the application and inject the mock.

    The exact name of the method to override will vary depending on your exact traits hierarchy which I cannot see from your question.

    // Create the mock
    val mockedThirdModule = Mockito.mock(classOf[ThirdModule])
    when(mockedThirdModule.foo).thenReturn(Future.successfull("ABC"))
    
    // Make the app use the mock
    override def fakeApplication(): Application = {
      GuiceApplicationBuilder()
        .overrides(bind[ThirdModule].toInstance(mockedThirdModule))
        .build()
    }
    
    // Retrieve the class to test from the app
    lazy val exampleClass = app.injector.instanceOf[ExampleClass]