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"}"""
}
}
}
Related
If I have a controller named HomeController that receives a request like GET /foo with a header X-Foo: Bar, I would like to create a WS client filter that will read the RequestHeader in the context and copy the header value to the outgoing WS request.
Example Controller:
import play.api.libs.ws.{StandaloneWSRequest, WSClient, WSRequest, WSRequestExecutor, WSRequestFilter}
import play.api.mvc._
import scala.concurrent.ExecutionContext
#Singleton
class HomeController #Inject()(cc: ControllerComponents,
myWsClient: MyWSClient)
(implicit executionContext: ExecutionContext)
extends AbstractController(cc) {
def index = Action.async {
myWsClient.url("http://www.example.com")
.get()
.map(res => Ok(s"${res.status} ${res.statusText}"))(executionContext)
}
}
The wrapper around WSClient that introduces the filter:
#Singleton
class MyWSClient #Inject()(delegate: WSClient, fooBarFilter: FooBarFilter) extends WSClient {
override def underlying[T]: T = delegate.underlying.asInstanceOf[T]
override def url(url: String): WSRequest = {
delegate.url(url)
.withRequestFilter(fooBarFilter)
}
override def close(): Unit = delegate.close()
}
And finally the WS filter itself:
#Singleton
class FooBarFilter extends WSRequestFilter {
override def apply(executor: WSRequestExecutor): WSRequestExecutor = {
(request: StandaloneWSRequest) => {
request.addHttpHeaders(("X-Foo", "<...>")) // INSERT CORRECT VALUE HERE!
executor.apply(request)
}
}
}
In the end, the expectation is that the request GET http://www.example.com contains the header X-Foo: Bar.
The special requirements that make this more interesting are:
You can modify the MyWsClient class.
You can modify the FooBarFilter class
You can create HTTP controller filters (play.api.mvc.(Essential)Filterif it helps.
You can create other classes/objects/etc
You cannot modify the controller (because in our situation, we can't expect all existing controllers to be modified.
The solution should work even if there's a a "service" layer between the controller and the WSClient invocation and doesn't involve passing down objects everywhere.
The solution can alter other Play/Akka mechanisms, like the default Dispatcher
I haven't tried to put it into actual code and test if this works but here is an idea: it looks like since Play 2.1 Http.Context is propagated even across async call. And there is Http.Context._requestHeader. So what you can try to do is to change MyWSClient and FooBarFilter like this:
#Singleton
class MyWSClient #Inject()(delegate: WSClient) extends WSClient {
override def underlying[T]: T = delegate.underlying.asInstanceOf[T]
override def url(url: String): WSRequest = {
val fooHeaderOption = Http.Context.current()._requestHeader().headers.get(FooHeaderFilter.fooHeaderName)
val baseRequest = delegate.url(url)
if (fooHeaderOption.isDefined)
baseRequest.withRequestFilter(new FooHeaderFilter(fooHeaderOption.get))
else
baseRequest
}
override def close(): Unit = delegate.close()
class FooHeaderFilter(headerValue: String) extends WSRequestFilter {
import FooHeaderFilter._
override def apply(executor: WSRequestExecutor): WSRequestExecutor = {
(request: StandaloneWSRequest) => {
request.addHttpHeaders((fooHeaderName, headerValue))
executor.apply(request)
}
}
}
object FooHeaderFilter {
val fooHeaderName = "X-Foo"
}
}
The idea is simple: extract the header from the Http.Context.current() when WSRequest is created and attach it to the request using a WSRequestFilter
Update: make it work in Scala API
As it was pointed out in the comment, this approach doesn't work in Scala API because Http.Context is not initialized and is not passed between threads. To make it work a higher level magic is required. Namely you need:
Easy: A Filter that will init Http.Context for Scala-handled requests
Hard: Override ExecutorServiceConfigurator for Akka's default dispatcher to create a custom ExecutorService that will pass Http.Context between thread switches.
The filter is trivial:
import play.mvc._
#Singleton
class HttpContextFilter #Inject()(implicit ec: ExecutionContext) extends EssentialFilter {
override def apply(next: EssentialAction) = EssentialAction { request => {
Http.Context.current.set(new Http.Context(new Http.RequestImpl(request), null))
next(request)
}
}
}
And the add it to the play.filters.enabled in the application.conf
The hard part is something like this:
class HttpContextWrapperExecutorService(val delegateEc: ExecutorService) extends AbstractExecutorService {
override def isTerminated = delegateEc.isTerminated
override def awaitTermination(timeout: Long, unit: TimeUnit) = delegateEc.awaitTermination(timeout, unit)
override def shutdownNow() = delegateEc.shutdownNow()
override def shutdown() = delegateEc.shutdown()
override def isShutdown = delegateEc.isShutdown
override def execute(command: Runnable) = {
val newContext = Http.Context.current.get()
delegateEc.execute(() => {
val oldContext = Http.Context.current.get() // might be null!
Http.Context.current.set(newContext)
try {
command.run()
}
finally {
Http.Context.current.set(oldContext)
}
})
}
}
class HttpContextExecutorServiceConfigurator(config: Config, prerequisites: DispatcherPrerequisites) extends ExecutorServiceConfigurator(config, prerequisites) {
val delegateProvider = new ForkJoinExecutorConfigurator(config.getConfig("fork-join-executor"), prerequisites)
override def createExecutorServiceFactory(id: String, threadFactory: ThreadFactory): ExecutorServiceFactory = new ExecutorServiceFactory {
val delegateFactory = delegateProvider.createExecutorServiceFactory(id, threadFactory)
override def createExecutorService: ExecutorService = new HttpContextWrapperExecutorService(delegateFactory.createExecutorService)
}
}
and register at using
akka.actor.default-dispatcher.executor = "so.HttpContextExecutorServiceConfigurator"
Don't forget to update the "so" with you real package. Also if you use more custom executors or ExecutionContexts, you should patch (wrap) them as well to pass Http.Context along the asynchronous calls.
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 => ??? }
My app hits many different dbs, which db depends on a query string parameter. I have a DatabaseConfigLocator that takes the string and returns the config and it works great. My problem is I want to make the config for each request available implicitly in my controller. I have tried two approaches.
class MyController extends Controller{
implicit def dbConfig(implicit request: RequestHeader): DatabaseConfig[JdbcProfile] = DatabaseConfigLocator.get[JdbcProfile](request.getQueryString("dbName")
}
This dosn't compile unless I change it to an implicit val with the same type but I need to recheck the query string every request not just once so I don't think an implicit val will work
The other approach was to create an action
object IODBAction extends ActionBuilder[Request]{
def invokeBlock[A](request: Request[A], block: (Request[A]) => Future[Result]) = {
implicit def dbConfig(implicit request: RequestHeader): DatabaseConfig[JdbcProfile] = DatabaseConfigLocator.get[JdbcProfile]("blah")
block(request)
}
}
but that implicit is not available in the scope of the block and I cant find any way to pass it in as an implicit.
My goal is to be able to do something like this
class MyController extends Controller {
def create = {
Action.async {
request =>
ApiResponse {
for {
id <- aProvider.save(validRegistrationRequest.toVisitor)
} yield id
}
}
}
}
class aProvider {
def save(v: Visitor)(implicit dbConfig: DatabaseConfig[JdbcProfile]): ApiResponse[VisitorId]
}
or if the provider can get the implicit at instantiation time all the better
class aProvider(implicit dbConfig: DatabaseConfig[JdbcPRofile]) {
def save(v: Visitor): ApiResponse[VisitorId]
}
Any advice on how to approach this or if its possible with play framework?
Unfortunately, I fear you're stuck with an action receiving a single parameter, so you need to stick to the standard play "Action Composition" pattern. This is documented pretty extensively in the play docs.
I'd define yourself a a "Context" like this:
case class Context(dbConfig: DatabaseConfig[JDBCProfile], request: Request[A])
extends WrappedRequest(request)
Then create a custom action builder like this:
object DBIOAction extends ActionBuilder[Context]{
def invokeBlock[A](request: Request[A], block: (Context[A]) => Future[Result]) = {
val dbConfig = DatabaseConfigLocator.get[JdbcProfile]("blah")
val context = Context(dbConfig, request)
block(context)
}
}
You then should be able to use it like this:
def index = DBIOAction { implicit context =>
// do some stuff. return a result
}
In order to make things simple, I'd pass the implicit context into your service methods, perhaps extracting the dbConfig from the context and passing it the rest of the way down to your DAO's.
class FunService {
def getSomeData(param1: String)(implicit context: Context) = {
// do some work, perhaps using context.dbConfig
}
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
}
}
}
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.