1

My application uses compile time injection. The loader is defined as follows (code snippet):

class AppLoader extends ApplicationLoader { ...}

class AppComponents (context: Context) extends BuiltInComponentsFromContext(context) {
...

//within this I have created instances of my controller and created a route
    lazy val userController = new UserController(userRepository, controllerComponents, silhouetteJWTProvider)

lazy val router = new Routes(httpErrorHandler, homeController,userWSRoutes, countController,asyncController, assets)

}

The UserController class has a signupUser Action

@Singleton
class UserController @Inject()(
userRepo: UsersRepository,cc: ControllerComponents, silhouette: Silhouette[JWTEnv])(implicit exec: ExecutionContext) extends AbstractController(cc){
...

def signupUser = silhouette.UserAwareAction.async{ implicit request => {
...
}
}

I want to test the signupUser Action but I don't know how to do it. I have created the following spec class but I am stuck at how to write the spec and test it.

class UserControllerSpec extends PlaySpec {


    "User signup request with non-JSON body" must {
      "return  400 (Bad Request) and the validation text 'Incorrect body type. Body type must be JSON'" in {

//I want to create instance of a `FakeRequest` annd pass it to UserController.signupUser. I should test a Future[Result] which I should then assert.

//How do I get instance of userController which I created in my Apploader? I don't want to repeat/duplicate the code of AppLoader here.

      }
    }
}
Manu Chadha
  • 15,555
  • 19
  • 91
  • 184

1 Answers1

0

Existing components from your ApplicationLoader can be directly instantiated within tests. Mixin WithApplicationComponents trait and override def components: BuiltInComponents:

override def components: BuiltInComponents = new YourComponents(context)

Here is an example implementation of your test:

import org.scalatestplus.play._
import org.scalatestplus.play.components.OneAppPerSuiteWithComponents
import play.api.BuiltInComponents
import play.api.mvc.Result
import play.api.libs.json.Json
import play.api.test.Helpers._
import play.api.test._
import scala.concurrent.Future

class UserControllerSpec extends PlaySpec with OneAppPerSuiteWithComponents {

  override def components: BuiltInComponents = new YourComponents(context)

  "User signup request with non-JSON body" should {

    "return  400 (Bad Request) and the validation text 'Incorrect body type. Body type must be JSON'" in {

      val Some(result): Option[Future[Result]] =
        route(
          app, 
          FakeRequest(POST, "/signup").withJsonBody(Json.parse("""{"bad": "field"}"""))
        )

      status(result) mustBe BAD_REQUEST
    }
  }
}

Helpers.stubControllerComponents is very useful for unit testing controllers. Here is an example of how it can be used to implement the same test without having to deal with ApplicationLoader.

import akka.actor.ActorSystem
import akka.stream.ActorMaterializer
import controllers.UserController
import org.scalatest.mockito.MockitoSugar
import org.scalatestplus.play._
import play.api.libs.json.Json
import play.api.test.Helpers._
import play.api.test._

class UserControllerSpec extends PlaySpec with MockitoSugar {

  "User signup request with non-JSON body" should {

    "return  400 (Bad Request) and the validation text 'Incorrect body type. Body type must be JSON'" in {

      implicit val actorSystem = ActorSystem()
      implicit val materializer = ActorMaterializer()

      val controller = new UserController(
        mock[UsersRepository]
        Helpers.stubControllerComponents(playBodyParsers = Helpers.stubPlayBodyParsers(materializer)),
        mock[Silhouette[JWTEnv]]
      )

      val result = 
        call(
          controller.signupUser, 
          FakeRequest(POST, "/signup").withJsonBody(Json.parse("""{"bad": "field"}"""))
        )

      status(result) mustBe BAD_REQUEST
    }
  }
}
Mario Galic
  • 47,285
  • 6
  • 56
  • 98
  • Thanks Mario. I just tried your code but faced several issues. I think I am facing name conflicts. I am seeing errors like `Cannot resolve symbo Helpers`,`cannot resolve symbol playBodyParsers`,`Cannot resolve symbol JWTEnv`. Interesting, when I hover the mouse over `JWTEnv`, the IDE recommends that I import the correct file `components.SilhouetteComponents` where `JWTEnv` is defined but importing it doesn't solve the issue. – Manu Chadha Jun 07 '18 at 08:00
  • I am also confused about using `new YourComponents(context)` when overriding `components`. In my code, I already have `AppComponents` defined `class AppComponents (context: Context) extends BuiltInComponentsFromContext(context)`. I tried to do `override def components: BuiltInComponents = new AppComponents(context)` but I get the error `cannot resolve symbol AppComponent`. – Manu Chadha Jun 07 '18 at 08:05
  • Mario - I was able to make the first test case work by adding line `package app;` in my `Apploader.scala`. However, I am not sure if this is the right thing to do. Could you please confirm? Basically the `AppLoader.scala` is in `projectdir/app/` while my specs are in `projectdir/test/ControllerSpec/UserController.spec`. Now the compiler can find my `AppComponent` class created in `AppLoader.scala`. Did I solve the problem correctly? – Manu Chadha Jun 07 '18 at 08:29
  • There are no hard rules here. `import` clause is [not required](https://docs.scala-lang.org/tour/packages-and-imports.html#imports) for accessing members of the same package. This includes tests, that is, a test in the same package as the entity being tested, will have access to that entity without requiring an `import` statement even though the test is under a separate `test/` directory. One popular convention is to have test package structure under `test/` mirror the package structure under `src/`. – Mario Galic Jun 07 '18 at 21:12
  • Thanks. I opened another issue for this - check the answer here https://stackoverflow.com/questions/50739913/class-is-not-visible-unless-i-declare-it-in-a-package/50741490?noredirect=1#comment88490790_50741490 – Manu Chadha Jun 08 '18 at 05:01