Integration testing with Play, Akka and Websockets - scala

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

Related

What does Action mean in play framework (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.

How to send message to actor via web socket in Play! framework?

This is rather a basic question but I couldn't find a satisfying answer after googling for hours. From the example in here, the way to make web socket is something like this:
Controller code:
import play.api.mvc._
import play.api.libs.streams.ActorFlow
import javax.inject.Inject
import akka.actor.ActorSystem
import akka.stream.Materializer
class Application #Inject()(cc:ControllerComponents) (implicit system: ActorSystem, mat: Materializer) extends AbstractController(cc) {
def socket = WebSocket.accept[String, String] { request =>
ActorFlow.actorRef { out =>
MyWebSocketActor.props(out)
}
}
}
Actor code:
import akka.actor._
object MyWebSocketActor {
def props(out: ActorRef) = Props(new MyWebSocketActor(out))
}
class MyWebSocketActor(out: ActorRef) extends Actor {
def receive = {
case msg: String =>
out ! ("I received your message: " + msg)
}
}
But how exactly do I send message from controller to the actor via web socket? Let's say in the controller code, I have an action code that handles when a button is pressed, it will send a block of string to the actor. How do I send this string to the actor above from the controller code?
I can provide you with some examples of websockets in Play. Essentially they use a Flow (akka-streams) to handle a websocket connection.
There is an official Play Websocket example: Lightbend's Websocket example
Based on that, I have several projects that use websockets, for example:
play-wsocket-scalajs
This is an example application showing how you can integrate a Play project with a Scala.js, Binding.scala project - using Web Sockets.
It is quite involved, so the easiest way is to check HomeController, UserParentActor, UserActor and AdapterActor how they work together.
scala-adapters
Is a framework that is based on the example above - that also shows how to register websocket clients.
Let's first understand what is already created, and what we need to add. The type of socket, is a WebSocket.
This WebSocket, reveals a single apply method:
def apply(request: RequestHeader): Future[Either[Result, Flow[Message, Message, _]]]
Therefore, as long as you did not send a message, the flow has not yet been created. Now, once a message is sent we can create the flow, and send it a meesage:
def index = Action.async(parse.json) { request =>
socket(request).map {
case Left(result) =>
Ok("Done: Left: " + result.body)
case Right(value) =>
Source.single(TextMessage(Json.stringify(request.body))).via(value).to(Sink.ignore).run()
Ok("Done: Right: ")
}
}
This sample app and related discussion might be helpful. Here's some code clipped/summarized from the linked sample app:
Flow.futureFlow(futureUserActor.map { userActor =>
val incomingMessages: Sink[Message, NotUsed] =
Flow[Message]
.map(...)
.to(...)
val outgoingMessages: Source[Message, NotUsed] =
ActorSource
.actorRef[User.OutgoingMessage](...)
.mapMaterializedValue { outActor =>
// give the user actor a way to send messages out
userActor ! User.Connected(outActor)
NotUsed
}
.map(...)
// then combine both to a flow
Flow.fromSinkAndSourceCoupled(incomingMessages, outgoingMessages)
})
There are at least two ways to approach this:
Customize play's ActorFlow.actorRef method to return the underlying actor. There was a similar discussion before, here's a gist. If you put the underlying actor into a (user, websocket) map, make sure to use a thread-safe implementation like the TrieMap.
What you're trying to do could be solved by creating an event bus & subscribing to it from within the actor. Then you could filter the events you're interested in & react accordingly. This solution is better, in that it actually scales - you can have more than one replica of your web app (the 1st approach wouldn't work in that case, because a replica that doesn't hold the reference to a user's WS actor could receive button clicked event). In pseudo code to illustrate the idea:
sealed trait AppEvent
final case class ButtonClicked(user: User.ID) extends AppEvent
// inside an action
system.eventStream.publish(ButtonClicked(request.identity.id))
// inside your actor
override def preStart =
context.system.eventStream.subscribe(self, classOf[AppEvent])
Please note that the idea of event bus is abstract. What I've demonstrated above is the most basic approach using akka's classic event bus, which works locally. For this approach to scale, you would need an actual message queue behind the scenes.

How to Test a Play Application that extends a custom trait

I'm having trouble writing tests for a mixin to my Play application that runs in it's own thread separate from play. I've tried over-writing WithApplication.provideApplication method with no luck. I get an inheriting conflicting methods error. (one from the real app "MyRunnableSystemWrapper", one from my mocked fake mixin called "MyMockedSystemWrapper").
execute(system) runs my system that is tested elsewhere and has sideaffects (connects to networked services, thus failing this test when such things are not available. Good news is I have a mocked service of my system wrapper that uses a system which does NOT have side affects and DB/Network calls are mocked out. However I do not know how to give THIS MOCKED version of my app to "WithApplication" test.
Reduced Code for clarity:
class Application extends Controller with MyRunnableSystemWrapper {
val pool: ExecutorService = Executors.newFixedThreadPool(1)
val system = new MyRunnableSystem() //system is abstract in MRSW ^^^ above
pool.execute(system)
def index = Action {
OK("HI")
}
}
My Test:
class MyAppTest(implicit ee: ExecutionEnv) extends Specification {
abstract class WithMyMockApp extends WithApplication {
def provideApplication = new controllers.Application with MyMockedSystemWrapper // This imports MyRunnableSystemWrapper
}
"Sending a GET request" should {
"Respond with OK" in new WithMyMockApp {
val response = route(app, FakeRequest(GET, "/")).get
status(response) mustEqual OK
}
}
}
If I'm not running my Runnable in the correct place and should be calling it somewhere else to make this testing easier, let me know!
You could inject your system wrapper instead of extending it
trait SystemWrapper {
def execute(system: RunnableSystem)
}
class MyRunnableSystemWrapper extends SystemWrapper {...}
class MyMockedSystemWrapper extends SystemWrapper {...}
class Application #Inject() (systemWrapper SystemWrapper) extends Controller {
Then you need to tell Guice which implementation of SystemWrapper you want for runtime and which one for test. One way of doing this is by using different Guice modules for runtime/test which you set in your .conf files.

Play 2.5 priming the web service

I have a restful web service developed using play 2.5 framework.
I want to prime my web service on start by calling itself. This is to make sure that my service is perfectly up and running.
The approach i am taking is using eagerBinding. But the code inside the class injected using eager binding gets executed just before the app starts
Here is what my eagerbinding code looks like
#Singleton
class PrimingMe #Inject()(ws: WSClient) {
isServicePrimed
def isServicePrimed: Boolean = {
println("PRIME ME!!!")
val response = ws.url("http://localhost:9000/index").get
.map {
response =>
response.status match {
case 200 => true
case _ => false
}
}
try {
Await.result(response, 5.second)
} catch {
case _ => false
}
}
}
class ServiceInjectionModule extends AbstractModule {
def configure(): Unit = {
bind(classOf[PrimingMe]).asEagerSingleton
}
}
Inside application.conf
play.modules.enabled += "util.ServiceInjectionModule"
I want to prime my application with a dummy service call so that when the real traffic starts coming all db connections are made. Currently my first api call to the service takes much longer than the usual. What other options do i have to achieve this.
No Purpose is not defeated. Eager loading still works as expected.
Ensure that the util.ServiceInjectionModule is the first module to load by declaring it on the top in the application.conf file.
I have done a small experiment after seeing your question to prove this
This is how my module looks like. It is declared in the root directory and as Module is in the root director i.e app, You do not have to explicitly add to the application.conf
class Module extends AbstractModule {
override def configure() = {
//It is very important for this call to be on the top.
bind(classOf[Initialize]).asEagerSingleton()
}
}
Eager singleton
import com.google.inject.Inject
import com.google.inject.Singleton
import play.api.Logger
import play.api.libs.ws.WSClient
import scala.concurrent.Await
import scala.concurrent.duration.Duration
#Singleton
class Initialize #Inject() (wsClient: WSClient) {
hit()
def hit(): Unit = {
val f = wsClient.url("http://www.google.com").get()
val result = Await.result(f, Duration.Inf)
Logger.info(s"status: ${result.status}")
}
}
output:
[info] application - status: 200
[info] play.api.Play - Application started (Dev)
From the above output you can see that Module has loaded and hit() is called.

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 ;)