What does Action mean in play framework (Scala)? - scala

I am new to Play Framework and was trying to understand SimpleRouter class. In the Play documentation, I see a simple example as:
val router = Router.from {
case GET(p"/hello/$to") =>
Action {
Results.Ok(s"Hello $to")
}
}
But I am not sure what is Action and what is its significance? I see it heavily used in all other routing methods as well. Here's the link to the documentation: https://www.playframework.com/documentation/2.8.x/ScalaSirdRouter
My reference code:
import play.api.mvc._
import play.api.routing.Router._
import play.api.routing._
import play.api.routing.sird._
class CacheRouter extends SimpleRouter {
override def routes: Routes = {
case GET(p"/") =>
Action {
Results.Ok(s"Hello")
}
// case POST(p"/") =>
// controller.process
//
// case GET(p"/$id") =>
// controller.show(id)
}
}

An Action according to the Play documentation here is
basically a (play.api.mvc.Request => play.api.mvc.Result) function that handles a request and generates a result to be sent to the client.
In other words, an Action a full implementation of the server side of the http request that is able to take a play.api.mvc.Request and do some work and return a play.api.mvc.Result
In your example the Action is one that does not use anything from the request and returns a static HTTP 200 response with the content hello
Edit: From the comment here it looks like you are having an issue with the Action in the code sample not being resolved.
The reason for this is that that is not actually a reference to the play.api.mvc.Action but a helper method to construct Actions.
This helper method is defined in play.api.mbc.AbstractController which you can extend to make use of it.
An example of this would be something like:
class CacheRouter #Inject()(cc: ControllerComponents) extends AbstractController(cc) with SimpleRouter {
override def routes: Routes = {
case GET(p"/") =>
Action {
Results.Ok(s"Hello")
}
}
}
}
Bear in mind the above is using Plays guice integration for dependency injection but that is just one of many options available.

Related

Play Scala Dependency injection: How to use it

I am trying to use Play 2.5 dependency injection. I have following class which makes a call to REST api and parses the response
class Client #Inject()(ws:WSClient, baseUrl: string) {
def this(ws:WSClient) = this(ws, "<url string>")
def getResponse() = {....}
....
}
The caller of the code looks like below
var client = new Client(WS.client)
client.getResponse()
I am getting following warning.
object WS in package ws is deprecated: Inject WSClient into your
component
I understand that i need to inject WS.Client instead of passing it explicitly to the Client constructor. But how do i do that?
=== Update ===
I don't want to inject Client or WSClient from the Controller. My controller creates objects and classes at run time and i want those objects to create Client Object. When i explicitly pass WS.client object to the Client object i get the above stated warning.
=== Update 2 ===
I have a plugin architecture in my application. When a a controller starts an action. It does not know what set of plugins it is going to execute. Some plugins would not need a WSClient and some of them would. So i dont want to couple the injection of WSClient into my controller. Each plugin independently decides if it wants to call a remote service. When a plugin decides to call the remote service, it should be able to inject WSClient in what ever client it wants to invoke.
Controller Action --> Determine Plugins to Execute --> Execute Plugins ---> Plugin1 (needs to call a remote api, create a client object, per say new Client(WS.Client)). This is where the injection should happen, not at the controller.
Ok. I will assume you have two classes. First we will have your Client class:
#Singleton // this is not necessary, I put it here so you know this is possible
class Client #Inject() (ws:WSClient, baseUrl: String) {
// Since this controller is not annotated with #Inject
// it WILL NOT be used when binding components
def this(ws:WSClient) = this(ws, "<url string>")
def getResponse() = {
// do something using ws object
}
}
Then you have another class that uses Client, per instance, a controller:
class MyController #Inject() (client: Client) extends Controller {
def someAction = Action {
// do something with client object
}
}
The main point here is that the controller did not need to create a Client instance. It was automatically injected by Guice.
Moreover, your client class needs a baseUrl and there is no place telling Play which value is needed there. If this is a configuration, than you can do something like this:
import play.api.Configuration
class Client #Inject() (ws:WSClient, configuration: Configuration) {
def getResponse() = {
val baseUrl = configuration.getString("key.to.baseUrl")
// do something using ws object and baseUrl
}
}
But, if you really want your Client object to receives a String, then we need to tell Play which String needs to be injected:
package com.acme.modules
import com.google.inject.AbstractModule
import com.google.inject.name.Names
class MyModule extends AbstractModule {
def configure() = {
bind(classOf[String])
.annotatedWith(Names.named("baseUrl")) // attention to the name here. It will be used below
.toInstance("http://api.example.com/")
}
}
And then enable this module by adding the following line to your application.conf:
play.modules.enabled += "com.acme.modules.MyModule"
After that, we will change Client to be specific about which String it is expecting:
import play.api.Configuration
// #Named needs to receive the same value defined at the module class.
class Client #Inject() (ws:WSClient, #Named("baseUrl") baseUrl: String) {
def getResponse() = {
val baseUrl = configuration.getString("key.to.baseUrl")
// do something using ws object and baseUrl
}
}
Update after question edit:
Give the structure you want/need:
Controller Action --> Determine Plugins to Execute --> Execute Plugins ---> Plugin1
Your code can also follow that path with classes like this:
MyController -> PluginResolver -> Plugin
-> PluginRunner ->
And, then, you can have:
Controller:
class MyController #Inject() (
pluginResolver: PluginResolver,
pluginRunner: PluginRunner
) extends Controller {
def action = Action {
val plugins = pluginsResolver.resolve(/* give a criteria to select plugins */)
val someResultFromPluginsExecution = pluginsRunner.run(plugins)
// map result from plugins execution to a play play.api.mvc.Result
// return the play.api.mvc.Result
}
}
Plugin classes:
import play.api.inject.Injector
class PluginResolver #Inject()(injector: Injector) {
def resolve(/* some criteria to resolve plugins */): Seq[Plugin] = {
val pluginsClasses = ... // find the necessary plugins based on the criteria
pluginsClasses.map { pluginClass => injector.instanceOf(pluginClass) }
}
}
// ExecutionContext is not really necessary, but maybe you want/need
// another thread pool to execute plugins
class PluginRunner #Inject()(implicit executionContext: ExecutionContext) {
def run(plugins: Seq[Plugin]): Seq[PluginExecutionResult] = {
// run the plugins
// return the result
}
}
trait Plugin {
def execute(): PluginExecutionResult
}
The real magic here happens at the PluginResolver. It uses a play.api.inject.Injector to create plugins instances and then your plugins can use Dependency Injection. Per instance:
class PluginThatNeedsWSClient #Inject(wsClient: WSClient) extends Plugin {
def execute(): PluginExecutionResult = {
// Use wsClient to call a remote service
// return the execution result
}
}
Reference:
Scala: Dependency Injection
Scala: Play WS API
play.api.inject.Injector
I saw this awesome post last week: http://www.schibsted.pl/2016/04/dependency-injection-play-framework-scala/

How to perform unit test in controller?

Let say, i have controller like the following:
class JenisKejahatanControl #Inject()(service: JenisKejahatanService, val messagesApi: MessagesApi) extends Controller with I18nSupport {
def add = Action.async { implicit request =>
lazy val incoming = JenisKejahatan.formJenisK.bindFromRequest()
incoming.fold( error => {
lazy val response = ErrorResponse(BAD_REQUEST, messagesApi("request.error"))
Future.successful(BadRequest(Json.toJson(response)))
}, { newJenisK =>
lazy val future = service.addJenisK(newJenisK)
future.flatMap {
case Some(jenis) => Future.successful(Created(Json.toJson(SuccessResponse(jenis))))
case None => Future.successful(BadRequest(Json.toJson(ErrorResponse(NOT_FOUND, messagesApi("add.jenis.kejahatan.fail")))))
}
})
}
}
and the i want to test my def add using specs2, how to do it?
Since your controller has injected components the bit I assume that one bit you're missing is how to obtain an instance of it in your spec with the various dependencies satisfied. For this you can use the GuiceApplicationBuilder to obtain a Play application instance, and then use its injector to get an instance of your controller without having to construct it manually (more dependency injection docs here and specifically about testing with Guice here.)
If you can construct your controller manually, as in the example, that's great and makes things simpler, but controllers tend to have non-trivial dependencies which you will most likely want to mock using the overrides method on the GuiceApplicationBuilder.
With an instance of your controller constructed, it's then really simple to "apply" a mock (fake) request to your action methods and determine that they give the status and body you expect. Here's an example:
import controllers.{SuccessResponse, JenisKejahatanControl}
import play.api.inject.guice.GuiceApplicationBuilder
import play.api.inject.bind
import play.api.mvc.Result
import play.api.test.{FakeRequest, PlaySpecification}
import scala.concurrent.Future
class JenisKejahatanControlSpec extends PlaySpecification {
"JenisKejahatanControl#add" should {
"be valid" in {
// Build an instance of a Play application using the
// default environment and configuration, and use it
// to obtain an instance of your controller with the
// various components injected.
val jenisController = new GuiceApplicationBuilder()
.overrides( // Mock the data service
bind[JenisKejahatanService]
.toInstance(new MockJenisKejahatanService))
.build()
.injector
.instanceOf[JenisKejahatanControl]
// Create a "fake" request instance with the appropriate body data
val request = FakeRequest().withFormUrlEncodedBody("name" -> "test")
// Apply the request to your action to obtain a response
val eventualResult: Future[Result] = jenisController.add.apply(request)
// Check the status of the response
status(eventualResult) must_== CREATED
// Ensure the content of the response is as-expected
contentAsJson(eventualResult).validate[SuccessResponse].asOpt must beSome.which { r =>
r.jenis.name must_== "test"
}
}
}
}

Integration testing with Play, Akka and Websockets

I'm trying to do some acceptance/integration tests with websockets in my Application (play, akka, scalatest...) which will be consumed by a mobile app. I have done some unit test for every actor (in the future, i want that this "unit" refer to more than one actor). Now I want to test the whole system.
For non websocket endpoint like:
package controllers
import play.api.mvc.{Action, Controller}
class StatusController extends Controller {
def get() = Action { implicit request =>
Ok
}
}
I have the test:
package acceptance
import org.scalatestplus.play.{OneServerPerSuite, PlaySpec}
import play.api.libs.ws.WS
import play.api.test.Helpers._
class StatusSpec extends PlaySpec with OneServerPerSuite {
"Server Status" should {
"work" in {
val response = await(WS.url(s"http://localhost:$port/status").get())
response.status mustBe OK
}
}
}
In this case everything looks fine and works :) the problem comes the controller which handles websockets:
#Singleton
class ChatController #Inject()(system: ActorSystem) extends Controller {
def socket() = WebSocket.tryAcceptWithActor[JsValue, JsValue] { implicit request =>
Future.successful(request.session.get("user") match {
case None => Left(Forbidden)
case Some(userId) => Right(TalkerSocket.props(userId))
})
}
}
I've looked for some manuals or tutorials for testing these things without luck. I've seen that it's possible to test this with Browser and Selenium ...
I want to open simulate two clients, open two websockets, send some messages from one of the clients and listen responses from another one. Do you think this approach is the correct one? Is it possible to do without browser?
Thanks

Play 2.0: FakeApplication calling a stub controller instead real one

This is a continuation of a prior question in which answer I saw that I had a misconception about the use of plugins in FakeApplication.
First I have a Model created as a trait and a object that implment it.
trait UserModel extends ModelCompanion[User, ObjectId] {
// ...
}
object User extends UserModel
Next, I have a Controller, created as a abstract class that receive a instance of UserModel, and its respective implementation that uses the User object.
abstract class UsersController extends Controller {
val userModel: UserModel
def sayHello = Action(parse.json) { request =>
// return a play Action. It doesn't use userModel
}
// Other methods
}
object Users extends UsersController(User)
In test directory, I created a UsersController Stub using a UserModel mock:
package controllers
import org.specs2.mock.Mockito
object UserControllersTest extends UsersController with Mockito {
val userModel = mock[models.UserModel]
}
Now I have my UsersControllers Spec test:
package controllers
import org.specs2.mutable.Specification
import play.api.libs.json.Json
import play.api.test._
import play.api.test.Helpers._
class UsersSayHelloSpec extends Specification {
running(FakeApplication()) {
"Users.SayHello" should {
def sendJson(jsonMap: Map[String, String], shouldBeCorrect: Boolean) = {
running(new FakeApplication(
additionalPlugins = Seq("controllers.UserControllersTest"))) {
// Preapration
val jsonRequisition = Json.toJson(jsonMap)
// ***************************************
// It will call UsersControllers.sayHello
val Some(result) = routeAndCall(FakeRequest(POST,
"/hello",
FakeHeaders(Map("Content-Type" -> Seq("application/json"))),
jsonRequisition))
// ***************************************
// ...
}
}
"Not process a empty String" in {
sendJson(Map.empty[String, String], false)
}
// Other tests calling sendJson ...
}
}
}
So my question is: How can I say to FakeApplication to use UserControllersTest instead the real UserControllers implementation when call "/hello" URL, in routeAndCall() calling?
There are two different part of your application which you might want to test:
The controller itself
The http router
When you test the controller itself, you typically create a request and you directly pass it to a controller method, which will generate the answer. You then perform some validation of the answer to verify the test result.
When you test the router, what you want to test is that the request is routed to the right controller method: you directly call the url and you check that the result is the one you expect from the controller you expected.
What you are trying to do does not make much sense to me:
if on route /hello I have a dummyController, will my post to the route
/hello works correctly?
Since the routes file is compiled into a Scala class which actually works as a router, if (and I am not sure) such a dynamic routing feature is available in play, you would have to:
Delete the existing route on the /hello path
Add a new route point to a different controller

How to test Zentasks sample app from Play 2.0

I play with Play 2.0, Scala version. Currently, I analyze Zentasks sample app.
One of the part of this app is authentication mechanism mostly covered in Secured trait. I'm wondering how I can test secured actions, ex. index from Projects controller.
For not-secured action, I'd probably do something like
val result = controllers.Projects.index(FakeRequest())
to run an action and get its result.
What should I do in case of the secured action?
Disclaimer: I'm totally new to both Scala and Play, so all hints are very valuable. Thanks!
There is a fix for the integrated approach to this in Playframewrk v2.1 I have a backport of the fix on the 2.0.x branch
Until it gets merged and released, here is what I did (it works on Play 2.0.3+):
I defined my own Helpers object in a libs package like so.
package libs
import play.api.mvc._
import play.api.libs.iteratee._
import play.api.libs.concurrent._
import play.api.test._
object Helpers {
def routeAndCall[T](request: FakeRequest[T]): Option[Result] = {
routeAndCall(this.getClass.getClassLoader.loadClass("Routes").asInstanceOf[Class[play.core.Router.Routes]], request)
}
/**
* Use the Router to determine the Action to call for this request and executes it.
*/
def routeAndCall[T, ROUTER <: play.core.Router.Routes](router: Class[ROUTER], request: FakeRequest[T]): Option[play.api.mvc.Result] = {
val routes = router.getClassLoader.loadClass(router.getName + "$").getDeclaredField("MODULE$").get(null).asInstanceOf[play.core.Router.Routes]
routes.routes.lift(request).map {
case a: Action[_] =>
val action = a.asInstanceOf[Action[T]]
val parsedBody: Option[Either[play.api.mvc.Result, T]] = action.parser(request).fold(
(a, in) => Promise.pure(Some(a)),
k => Promise.pure(None),
(msg, in) => Promise.pure(None)
).await.get
parsedBody.map{resultOrT =>
resultOrT.right.toOption.map{innerBody =>
action(FakeRequest(request.method, request.uri, request.headers, innerBody))
}.getOrElse(resultOrT.left.get)
}.getOrElse(action(request))
}
}
}
Then in my test I import my Helpers and the whole play Helpers context, except for routeAndCall :
import libs.Helpers._
import play.api.test.Helpers.{routeAndCall => _,_}
I then use an Around to setup my app (I need the provide an application.secret as I store the authenticated user name in the session which is based on a signed cookie)
def appWithSecret():Map[String,String]={
Map(("application.secret","the answer is 42 !"))
}
object emptyApp extends Around {
def around[T <% Result](t: => T) = {
running(FakeApplication(additionalConfiguration = inMemoryMongoDatabase("emptyApp")++appWithSecret())) {
User(new ObjectId, "Jane Doe", "foobar#example.com", "id1").save()
t // execute t inside a http session
}
}
}
This allows me to write the following tests:
"respond to the index Action" in emptyApp {
val request: FakeRequest[AnyContent] = FakeRequest(GET, "/expenses").withSession(("email", "foobar#example.com"))
val Some(result) = routeAndCall(request)
status(result) must equalTo(OK)
contentType(result) must beSome("application/json")
charset(result) must beSome("utf-8")
contentAsString(result) must contain("Hello Bob")
}
It allows you to exercise the secured code even though it is not a unit test.
ok, I am no great expert either, but here is an idea.
Create a trait InSecure trait extends Secured which overrides the Secured actions and always permits access.
Then you can make an object InSecureProjects extends Projects with InSecture in your test, this should override just the security checks and let you test the actions without any security.
Now, instead of running the tests on Projects, you run them on InSecureProjects. You can do exactly the same for the other secured controllers.
I haven't tested it, so let me know if it works ;)