I want to write a unit test to test my custom action builder.
Once I set the ActionWithSession as in instance in my test, how do I instantiate it like it would be in a controller?
How can I test for the edge cases if it redirects or returns with the Session?
class SessionAwareRequest[A](val context: MyContext, request: Request[A]) extends WrappedRequest[A](request)
class ActionWithSession #Inject()(siteConfig: SiteConfig, userService: UserService)
extends ActionBuilder[SessionAwareRequest] {
def invokeBlock[A](request: Request[A], block: (SessionAwareRequest[A]) => Future[Result]) = {
val session = loadSession(request)
if(session.isDefined) {
val result: Future[Result] = block(new SessionAwareRequest(session.get, request))
result.map(_.withCookies(CookieHelper.newSession(session.get)(request)))
}
else {
Future.successful(Results.Redirect(routes.MessagesController.show("error!")))
}
def loadSession(requestHeader: RequestHeader): Option[MySession] = {
...
Some(MySession(...))
else
None
}
}
If you're using Play 2.3, you should be able to utilize the PlaySpecification trait, which provides a call method to test Actions. Have a look at the Documentation, especially the section about "Unit Testing EssentialAction
".
Here's an artificial, minimal example based on the code you posted. It basically just checks whether the request headers contains a "foo" field and if so, returns OK together with an additional cookie "baz" that is added via the custom action builder. If the "foo" header is missing it returns BadRequest. Hope it helps.
import play.api.mvc._
import play.api.test.{FakeRequest, PlaySpecification}
import scala.concurrent.Future
class ActionSpec extends PlaySpecification {
case class MySession()
class SessionAwareRequest[A](val context: MySession, request: Request[A]) extends WrappedRequest[A](request)
object ActionWithSession extends ActionBuilder[SessionAwareRequest] {
def loadSession(requestHeader: RequestHeader): Option[MySession] = {
if (requestHeader.headers.get("foo").isDefined)
Some(MySession())
else
None
}
def invokeBlock[A](request: Request[A], block: (SessionAwareRequest[A]) => Future[Result]): Future[Result] = {
val session = loadSession(request)
if (session.isDefined) {
val result: Future[Result] = block(new SessionAwareRequest(session.get, request))
result.map(_.withCookies(Cookie("baz", "quux")))
} else {
Future.successful(Results.BadRequest)
}
}
}
"My custom action" should {
"return HTTP code 400 in case the request header does not contain a 'foo' field" in {
val foo = call(ActionWithSession { request =>
Results.Ok("Yikes")
}, FakeRequest())
status(of = foo) must beEqualTo(BAD_REQUEST)
cookies(of = foo).get("baz") must beNone
}
"return HTTP code 200 in case the request header does contain a 'foo' field" in {
val foo = call(ActionWithSession { request =>
Results.Ok("Yikes")
}, FakeRequest().withHeaders("foo" -> "bar"))
status(of = foo) must beEqualTo(OK)
cookies(of = foo).get("baz") must beSome
}
}
}
Related
I would like to create a custom action which takes away the boilerplate of writing actions like this:
Action[MyClass](BodyParsers.parse.json[MyClass]) { req => ...
However, I keep running into class definition errors. Here has been my most successful attempt:
class JsonAction[A: Reads] extends ActionBuilder[Request] {
def hardcodedJson[A: Reads](action: Action[A]) =
Action.async(BodyParsers.parse.json[A]) { request => action(request) }
def invokeBlock[A: Reads](request: Request[A], block: (Request[A]) => Future[Result]) = {
block(request)
}
override def composeAction[A: Reads](action: Action[A]) = hardcodedJson(action)
}
but I get the following error: method composeAction overrides nothing.
If I change composeAction[A: Reads] to composeAction[A] it tells me there isn't a Json Serializer for type A.
Is there some other way to define this custom action?
Yep, I tried to get this to work with the official ActionBuilder way-of-doing-it and just could not get the types to line up.
Here's something that worked for me though:
object JsonActionHelper {
def withReads[A](act: Request[A] => Future[Result])(implicit reads:Reads[A]) =
Action.async(BodyParsers.parse.json(reads))(act)
}
Usage in your controller (FooJson is an object containing an implicit Reads[Foo]):
import models.FooJson._
import JsonActionHelper._
def handleIncomingFoo(fooId:String) = withReads[Foo] { req =>
val foo:Foo = req.body
...
Ok(...)
}
ActionBuilder is not generic enough for your use-case; there's nowhere for you to pass in your Reads[T].
There's nothing special about ActionBuilder though. It's a collection of apply and async factory methods. You can define your own Action type with whatever factory methods you need:
object JsonAction {
def apply[A : Reads](request: Request[A] => Result) = Action(BodyParsers.parse.json[A])(request)
}
// these are equivalent:
Action[MyClass](BodyParsers.parse.json[MyClass]) { req => ??? }
JsonAction[MyClass] { req => ??? }
I am following this guide -
https://www.playframework.com/documentation/2.1.1/JavaGuide4#Implementing-authenticators
I have implemented the authenticator as below in Scala:
package controllers
import play.mvc.Security
import play.mvc.Http.Context
import play.mvc.Result
import play.mvc.Results
class Secured extends Security.Authenticator {
override def getUsername(ctx: Context) : String = {
return ctx.session().get("username");
}
override def onUnauthorized(ctx: Context) : Result = {
Results.redirect(routes.Application.login())
}
}
I applied it as an annotation to the hello action in the below controller.
package controllers
import services.UserServiceImpl
import models.User._
import play.api.mvc._
import play.mvc.Security.Authenticated
object Application extends Controller {
def index = Action {
Ok(views.html.index("Your new application is ready."))
}
#Authenticated(classOf[Secured])
def hello = Action {
Ok("Hello")
}
def login = Action {
Ok("Login")
}
}
I am using Play 2.4 and play.api.mvc.Controller instead of play.mvc.Controller as in the guide. Is this the reason this is not working? How do I get it to work with play.api.mvc.Controller?
I figured an example would probably help explain things. If you want similar functionality in Play Scala you would do something like the following:
object AuthenticatedAction extends ActionBuilder[Request] {
def invokeBlock[A](request: Request[A], block: (Request[A]) => Future[Result]) = {
request.session.get("username") match {
case Some(user) => block(request)
case None => Redirect(routes.Application.login())
}
}
You would then use this in your controller:
def hello = AuthenticatedAction { implicit request =>
Ok("Hello")
}
Trying to better understand creating custom actions using action builder.
I have a custom action like:
class LogginInRequest[A](val currentUser: User, request: Request[A]) extends WrappedRequest[A](request)
object ActionWithContext extends ActionBuilder[LogginInRequest] {
def invokeBlock[A](request: Request[A], block: (LogginInRequest[A]) => Future[SimpleResult]) = {
val u = /// load User
block(new LogginInRequest(user, request)
}
}
Could I someone create another custom action like the above that inherits from the above one and builds upon it i.e. chain in the action and access the user object
e.g. Say I now create a new section on my website for admins, so I check the user object in this new action for:
user.isAdmin
If isAdmin is false, then I redirect using the Future.successful(Forbidden) call.
So I would create LoggedInAdmin based on LogginInRequest but simply checking for the property isAdmin (this is just an example but I want to know if I can do this).
Is it possible to pass an argument to my custom action that I created using action builder so I can do this?
def someAction(...):= MyCustomActionBuilder(userService) {
// ...
}
i.e. I am passing in a argument to it.
With Play 2.3, action composition using ActionBuilder got a whole lot better. The example below shows how you can define a standard ActionBuilder that creates a wrapped request, and then chain that builder together with an ActionTransformer to create a new wrapped request.
import scala.concurrent.Future
import org.scalatest.{MustMatchers, WordSpec}
import play.api.mvc._
import play.api.test.FakeRequest
import play.api.test.Helpers._
class ChainingTest extends WordSpec with MustMatchers {
class WrappedReq1[A](val str: String, request: Request[A]) extends WrappedRequest[A](request)
object ActionWithReq1 extends ActionBuilder[WrappedReq1] {
def invokeBlock[A](request: Request[A], block: (WrappedReq1[A]) => Future[Result]) = {
block(new WrappedReq1(request.headers("1"), request))
}
}
class WrappedReq2[A](val str1: String, val str2: String, request: Request[A]) extends WrappedRequest[A](request)
val ActionWithReq2 = ActionWithReq1 andThen new ActionTransformer[WrappedReq1, WrappedReq2] {
override protected def transform[A](request: WrappedReq1[A]): Future[WrappedReq2[A]] = {
Future.successful(new WrappedReq2[A](request.str, request.headers("2"), request))
}
}
"chained actions" should {
"work" in {
val request = FakeRequest().withHeaders("1" -> "one", "2" -> "two")
val response = ActionWithReq2 { r => Results.Ok(r.str1 + "-" + r.str2) }(request)
contentAsString(response) must equal("one-two")
}
}
}
There is an ActionTransformer trait for modifying your request wrapper, and an ActionFilter for intercepting certain requests and immediately returning a result.
ScalaActionsComposition describes the traits in more detail.
This is a simple example of a controller in Play Framework where every action checks the session - if the user is logged in.
object Application extends Controller {
def index = Action { implicit request =>
if (request.session.isEmpty) {
Redirect("/login")
} else {
Ok(views.html.index("index"))
}
}
def about = Action { implicit request =>
if (request.session.isEmpty) {
Redirect("/login")
} else {
Ok(views.html.index("about"))
}
}
}
I'd like to handle the session checking in the constructor instead of every action method, but I just don't know how? It should look something like this:
object Application extends Controller {
//This is where the constructor would check if session exists
//and if not - redirect to login screen
def index = Action {
Ok(views.html.index("index"))
}
def about = Action {
Ok(views.html.index("about"))
}
}
Is this possible and if so then how?
My stack is Play Framework 2.2.1, Scala 2.10.3, Java 1.8.0-ea 64bit
UPDATE - SOLVED Thanks for all your ideas, solution is now found, see my answer.
You could take advantage of Action Composition to achieve this. From the documentation:
import play.api.mvc._
class AuthenticatedRequest[A](val username: String, request: Request[A]) extend WrappedRequest[A](request)
object Authenticated extends ActionBuilder[AuthenticatedRequest] {
def invokeBlock[A](request: Request[A], block: (AuthenticatedRequest[A]) =>Future[SimpleResult]) = {
request.session.get("username").map { username =>
block(new AuthenticatedRequest(username, request))
} getOrElse {
Future.successful(Forbidden)
}
}
}
And then you could simply do:
def index = Authenticated {
Ok(views.html.index("index"))
}
Alternatively you could set up a filter instead (as #Robin Green suggested) like so:
object AuthFilter extends Filter {
override def apply(next: RequestHeader => Result)(rh: RequestHeader): Result = {
rh.session.get("username").map { user =>
next(rh)
}.getOrElse {
Redirect("/login")
}
}
In Global.scala scala, add
override def doFilter(action: EssentialAction) = AuthFilter(action)
For more on Filters, see the official docs
Solution is to use Action Composition and create a custom action.
Auth.scala:
package core
import play.api.mvc._
import scala.concurrent._
import play.api.mvc.Results._
object AuthAction extends ActionBuilder[Request] {
def invokeBlock[A](request: Request[A], block: (Request[A]) => Future[SimpleResult]) = {
if (request.session.isEmpty) {
//authentication condition not met - redirect to login page
Future.successful(Redirect("/login"))
} else {
//proceed with action as normal
block(request)
}
}
}
Application.scala:
package controllers
import play.api._
import play.api.mvc._
import core._
object Application extends Controller {
def index = AuthAction {
Ok(views.html.index("You are logged in."))
}
}
Take a look at Deadbolt: https://github.com/schaloner/deadbolt-2 . There are exhaustive examples and guides.
Works perfectly in my Play 2 project.
You could use a Filter, which applies to every request in the application. However, then you would need to have some code in that Filter to allow certain URLs to be accessed without a valid session, otherwise then the user would not be able to login in the first place.
I'm trying to test controller, which is using new Action.async. Following documentation I have excluded part under controller I want to test to separate trait with type reference:
trait UserController { this: Controller =>
def index() = Action { /* snip */ }
def register() = Action.async(parse.json) { request => /* snip */ }
}
Documentation states that I'm supposed to test it as:
object UsersControllerSpec extends PlaySpecification with Results {
class TestController() extends Controller with UserController
"index action" should {
"should be valid" in {
val controller = new TestController()
val result: Future[SimpleResult] = controller.index().apply(FakeRequest())
/* assertions */
}
}
}
}
For index() method it works perfectly, unfortunately I'm not able to do the same with register(), as applying FakeRequest on it returns instance of Iteratee[Array[Byte], SimpleResult]. I've noticed it has run() method that returns Future[SimpleResult] but no matter how I build FakeRequest it returns with 400 without any content or headers. Seems to me like content of FakeRequest is disregarded at all. Am I supposed to feed request body to iteratee somehow and then run it? I couldn't find any example how could I do that.
This problem arises because play.api.mvc.Action[A] contains these two apply methods:
// What you're hoping for
def apply(request: Request[A]): Future[Result]
// What actually gets called
def apply(rh: RequestHeader): Iteratee[Array[Byte], Result]
This arises because Request[A] extends RequestHeader, and the A in this case makes all the difference. If it's not the right type, you'll end up calling the wrong apply.
When you use ActionBuilder with a BodyParser[A], you create an Action[A]. As a result, you'll need a Request[A] to test. parse.json returns a BodyParser[JsValue], so you need a Request[JsValue].
// In FakeRequest object
def apply(): FakeRequest[AnyContentAsEmpty.type]
FakeRequest() doesn't get you the type you need. Fortunately:
// In FakeRequest class
def withBody[B](body: B): FakeRequest[B]
So, start writing your test by using a placeholder for the body:
"should be valid" in {
val controller = new TestController()
val body: JsValue = ??? // Change this once your test compiles
// Could do these lines together, but this shows type signatures
val request: Request[JsValue] = FakeRequest().withBody(body)
val result: Future[Result] = controller.index().apply(request)
/* assertions */
}
For me works this:
import concurrent._
import play.api.libs.json._
import play.api.mvc.{SimpleResult, Results, Controller, Action}
import play.api.test._
import ExecutionContext.Implicits.global
trait UserController {
this: Controller =>
def index() = Action {
Ok("index")
}
def register() = Action.async(parse.json) {
request =>
future(Ok("register: " + request.body))
}
}
object UsersControllerSpec extends PlaySpecification with Results {
class TestController() extends Controller with UserController
"register action" should {
"should be valid" in {
val controller = new TestController()
val request = FakeRequest().withBody(Json.obj("a" -> JsString("A"), "b" -> JsString("B")))
val result: Future[SimpleResult] = controller.register()(request)
/* assertions */
contentAsString(result) === """register: {"a":"A","b":"B"}"""
}
}
}