Search code examples
scalatestplayframework-2.6

How to test a controller in an application which uses compile time injection


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.

      }
    }
}

Solution

  • 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
        }
      }
    }