Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to test a Scala Play Framework websocket?

If I have a websocket like the following:

def websocket: WebSocket = WebSocket.accept[String, String] { _ =>
  ActorFlow.actorRef(out => LightWebSocketActor.props(out))
}

For reference, this is the LightWebSocketActor:

class LightWebSocketActor(out: ActorRef) extends Actor {
  val topic: String = service.topic

  override def receive: Receive = {
    case message: String =>
      play.Logger.debug(s"Message: $message")
      PublishService.publish("true")
      out ! message
  }
}

object LightWebSocketActor {
  var list: ListBuffer[ActorRef] = ListBuffer.empty[ActorRef]
  def props(out: ActorRef): Props = {
    list += out
    Props(new LightSocketActor(out))
  }

  def sendMessage(message: String): Unit = {
    list.foreach(_ ! message)
  }
}

This is using the akka websocket approach.

  • How should a test for this kind of controller be created?
  • How should I send information an expect a response?
  • What kind of information should be sent in the fake request?

For example I have this test for a regular html-returning controller:

"Application" should {
  "render the index page" in new WithApplication {
    val home = route(app, FakeRequest(GET, "/")).get
    status(home) must equalTo(OK)
    contentType(home) must beSome.which(_ == "text/html")
    contentAsString(home) must contain ("shouts out")
  }
}
like image 499
Camilo Sampedro Avatar asked Mar 30 '17 22:03

Camilo Sampedro


2 Answers

Play 2.6

I followed this Example: play-scala-websocket-example

Main steps:

Create or provide a WebSocketClient that you can use in your
tests.

Create the client:

val asyncHttpClient: AsyncHttpClient = wsClient.underlying[AsyncHttpClient]
val webSocketClient = new WebSocketClient(asyncHttpClient)

Connect to the serverURL:

val listener = new WebSocketClient.LoggingListener(message => queue.put(message))
val completionStage = webSocketClient.call(serverURL, origin, listener)
val f = FutureConverters.toScala(completionStage)

Test the Messages sent by the Server:

whenReady(f, timeout = Timeout(1.second)) { webSocket =>
  await().until(() => webSocket.isOpen && queue.peek() != null)

  checkMsg1(queue.take())
  checkMsg2(queue.take())
  assert(queue.isEmpty)
}

For example, like:

  private def checkMsg1(msg: String) {
    val json: JsValue = Json.parse(msg)
    json.validate[AdapterMsg] match {
      case JsSuccess(AdapterNotRunning(None), _) => // ok
      case other => fail(s"Unexpected result: $other")
    }
  }

The whole example can be found here: scala-adapters (JobCockpitControllerSpec)

like image 67
pme Avatar answered Sep 20 '22 17:09

pme


Adapted to Playframework 2.7

import java.util.concurrent.ExecutionException
import java.util.function.Consumer

import com.typesafe.scalalogging.StrictLogging
import play.shaded.ahc.org.asynchttpclient.AsyncHttpClient
import play.shaded.ahc.org.asynchttpclient.netty.ws.NettyWebSocket
import play.shaded.ahc.org.asynchttpclient.ws.{WebSocket, WebSocketListener, WebSocketUpgradeHandler}

import scala.compat.java8.FutureConverters
import scala.concurrent.Future

class LoggingListener(onMessageCallback: Consumer[String]) extends WebSocketListener with StrictLogging {

  override def onOpen(websocket: WebSocket): Unit = {
    logger.info("onClose: ")
    websocket.sendTextFrame("hello")
  }

  override def onClose(webSocket: WebSocket, i: Int, s: String): Unit =
    logger.info("onClose: ")

  override def onError(t: Throwable): Unit =
    logger.error("onError: ", t);

  override def onTextFrame(payload: String, finalFragment: Boolean, rsv: Int): Unit = {
    logger.debug(s"$payload $finalFragment $rsv")
    onMessageCallback.accept(payload)
  }

}

class WebSocketClient(client: AsyncHttpClient) {

  @throws[ExecutionException]
  @throws[InterruptedException]
  def call(url: String, origin: String, listener: WebSocketListener): Future[NettyWebSocket] = {
    val requestBuilder   = client.prepareGet(url).addHeader("Origin", origin)
    val handler          = new WebSocketUpgradeHandler.Builder().addWebSocketListener(listener).build
    val listenableFuture = requestBuilder.execute(handler)
    FutureConverters.toScala(listenableFuture.toCompletableFuture)
  }
}

And in test:

  val myPublicAddress = s"localhost:$port"
  val serverURL       = s"ws://$myPublicAddress/api/alarm/ws"

  val asyncHttpClient            = client.underlying[AsyncHttpClient]
  val webSocketClient            = new WebSocketClient(asyncHttpClient)
  val origin                     = "ws://example.com/ws"
  val consumer: Consumer[String] = (message: String) => logger.debug(message)
  val listener                   = new LoggingListener(consumer)
  val f                          = webSocketClient.call(serverURL, origin, listener)
  Await.result(f, atMost = 1000.millis)
like image 26
Andrzej Jozwik Avatar answered Sep 18 '22 17:09

Andrzej Jozwik