how to mock external WS API calls in Scala Play framework - scala

I have an existing Scala play application which has a REST API that calls another external REST API. I want to mock the external Web service returning fake JSON data for internal tests. Based on example from: https://www.playframework.com/documentation/2.6.x/ScalaTestingWebServiceClients
I followed example exactly as in Documentation and I'm getting compiler errors due to deprecated class Action.
import play.core.server.Server
import play.api.routing.sird._
import play.api.mvc._
import play.api.libs.json._
import play.api.test._
import scala.concurrent.Await
import scala.concurrent.duration._
import org.specs2.mutable.Specification
import product.services.market.common.GitHubClient
class GitHubClientSpec extends Specification {
import scala.concurrent.ExecutionContext.Implicits.global
"GitHubClient" should {
"get all repositories" in {
Server.withRouter() {
case GET(p"/repositories") => Action {
Results.Ok(Json.arr(Json.obj("full_name" -> "octocat/Hello-World")))
}
} { implicit port =>
WsTestClient.withClient { client =>
val result = Await.result(
new GitHubClient(client, "").repositories(), 10.seconds)
result must_== Seq("octocat/Hello-World")
}
}
}
}
}
object Action in package mvc is deprecated: Inject an ActionBuilder
(e.g. DefaultActionBuilder) or extend
BaseController/AbstractController/InjectedController
And this is the primary example from latest official docs which in fact contains a compile time error, given this example doesn't work how should be the proper way to easily mock an external API using Scala Play?

You may change your example to:
Server.withRouterFromComponents() { cs => {
case GET(p"/repositories") => cs.defaultActionBuilder {
Results.Ok(Json.arr(Json.obj("full_name" -> "octocat/Hello-World")))
}
}
} { implicit port =>
WsTestClient.withClient { client =>
val result = Await.result(
new GitHubClient(client, "").repositories(), 10.seconds)
result should be(Seq("octocat/Hello-World"))
}
}
To be honest, I'm not 100% sure if this is the nicest way. However I have submitted a PR to the play framework so you might watch that space for comments from the makers.

If you're using standalone version of play-ws you can use this library https://github.com/f100ded/play-fake-ws-standalone like this
import akka.actor.ActorSystem
import akka.stream.ActorMaterializer
import org.f100ded.play.fakews._
import org.scalatest._
import play.api.libs.ws.JsonBodyWritables._
import scala.concurrent.duration.Duration
import scala.concurrent._
import scala.language.reflectiveCalls
/**
* Tests MyApi HTTP client implementation
*/
class MyApiClientSpec extends AsyncFlatSpec with BeforeAndAfterAll with Matchers {
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
import system.dispatcher
behavior of "MyApiClient"
it should "put access token to Authorization header" in {
val accessToken = "fake_access_token"
val ws = StandaloneFakeWSClient {
case request # GET(url"http://host/v1/foo/$id") =>
// this is here just to demonstrate how you can use URL extractor
id shouldBe "1"
// verify access token
request.headers should contain ("Authorization" -> Seq(s"Bearer $accessToken"))
Ok(FakeAnswers.foo)
}
val api = new MyApiClient(ws, baseUrl = "http://host/", accessToken = accessToken)
api.getFoo(1).map(_ => succeed)
}
// ... more tests
override def afterAll(): Unit = {
Await.result(system.terminate(), Duration.Inf)
}
}

Related

could not find implicit value for parameter env: com.mohiva.play.silhouette.api.Environment[utils.auth.DefaultEnv]

I'm using a Silhouette v4.0 library with play framework 2.5.
And have been trying to write test code using play specs2.
But, I get the following error with my test class as below.
Error Message
[error] could not find implicit value for parameter env: com.mohiva.play.silhouette.api.Environment[utils.auth.DefaultEnv]
.withAuthenticator[DefaultEnv](identity.loginInfo)
^
Here's the test class
package controllers
import com.google.inject.AbstractModule
import org.joda.time.DateTime
import org.specs2.specification.Scope
import org.specs2.matcher._
import org.specs2.mock._
import play.api.test._
import play.api.libs.json._
import play.api.libs.json.Json
import play.api.libs.json.Reads._
import play.api.libs.functional.syntax._
import play.api.libs.concurrent.Execution.Implicits._
import play.api.libs.mailer.{ MailerClient, Email }
import play.api.inject.guice.GuiceApplicationBuilder
import play.api.inject.bind
import com.mohiva.play.silhouette.test._
import com.mohiva.play.silhouette.api._
import com.mohiva.play.silhouette.api.repositories.AuthInfoRepository
import com.mohiva.play.silhouette.api.util._
import com.mohiva.play.silhouette.impl.providers._
import net.codingwell.scalaguice.ScalaModule
import utils.auth.DefaultEnv
class TestControllerSpec extends PlaySpecification with Mockito {
"case" in new Context {
new WithApplication(application) {
val request = FakeRequest(POST, "/api/test")
.withAuthenticator[DefaultEnv](identity.loginInfo) // <-
val result = route(app, request).get
status(result) must be equalTo OK
}
}
trait Context extends Scope {
val identity = User(
loginInfo = LoginInfo(..)
..
)
implicit val env = FakeEnvironment[DefaultEnv](Seq(identity.loginInfo -> identity))
class FakeModule extends AbstractModule with ScalaModule {
def configure() = {
bind[Environment[DefaultEnv]].toInstance(env)
}
}
lazy val application = new GuiceApplicationBuilder()
.overrides(new FakeModule)
.build
}
}
There are some other test classes similar to this class are properly able to compile and execute.
It's kind of implicit problem with scope..
Therefore, I tried to import all the same as another test class which's able to compile properly. But, still unable to compile.
Missing some import?
As the compiler states, you're missing an implicit value. Use the following, which is modeled after one of Silhouette's specs:
class TestControllerSpec extends PlaySpecification with Mockito {
"the POST request" should {
"return an OK response" in new Context {
new WithApplication(application) {
val identity = User(LoginInfo(...))
implicit val env = FakeEnvironment[DefaultEnv](Seq(identity.loginInfo -> identity))
val request = FakeRequest(POST, "/api/test")
.withAuthenticator(identity.loginInfo)
val result = route(app, request).get
status(result) must be equalTo OK
}
}
}
trait Context extends Scope {
...
}
}

scala play framework how to unit test async controllers

Using Scala play version 2.5 and trying to follow the guidelines for unit testing controllers as per documentation at: https://www.playframework.com/documentation/2.5.x/ScalaTestingWithScalaTest
there's no example for unit testing an async controller.
I'm trying to create a unit test for my controller which has an async action method, I ended up mocking some objects
class ProductController #Inject()(
action: ProductAction,
handler: ProductResourceHandler)(implicit ec: ExecutionContext)
extends Controller {
/**
* Fetch a list of products
*/
def index: Action[AnyContent] = {
action.async { implicit request =>
handler.find.map { list =>
Ok(Json.toJson(list))
}
}
}
// ...
}
My unit test:
import scala.concurrent.Future
import org.scalatestplus.play._
import play.api.mvc._
import play.api.test._
import play.api.test.Helpers._
import org.scalatest.mockito.MockitoSugar
import product.ProductAction
import product.ProductController
import product.services.maps.GeolocationService
import product.ProductResourceHandler
import play.api.libs.concurrent.Execution.Implicits._
import scala.io.Source
import play.api.libs.json.Json
import product.model.OfferList
import product.model.OfferDetail
import org.mockito.Mockito._
class ProductControllerSpec extends PlaySpec with Results with MockitoSugar {
private val productList = Json.parse(Source.fromFile("conf/app/sample_products.json").getLines.mkString).as[ProductList]
"Example Page#index" should {
"should be valid" in {
val action = new ProductAction()
val handler = mock[ProductResourceHandler]
when(handler.find) thenReturn Future.successful(productList)
val controller = new ProductController(action, handler)
val result: Future[Result] = controller.index().apply(FakeRequest())
val bodyText: String = contentAsString(result)
bodyText != null mustBe true
}
}
}
up till now it's working but I'm wondering if this follows the best practices or guidelines for this type of test. Is this the right way to unit test an async controller in Scala play framework?
Some of my suggestions are
use contentAsJson instead of contentAsString and inspect the returned json.
use route to directly invoke the controller and test response (for eg route(app, FakeRequest..)
use status method to check if the returned status is HTTP OK (status code 200)
val Some(result) = route(app, FakeRequest(GET,
controllers.routes. ProductController.index.path()))
status(result) must be (OK)
val json = contentAsJson(result)
// inspect json fields like if you have to check if the json
// has string field called id you can do (json \ "id").as[String] must be ("<id value>")
According to Play documentation:
Play actions are asynchronous by default.
This means even if you are not using Action.async { Future { myAnonymousFunction } } but just Action { myAnonymousFunction }, internally the result of myAnonymousFunction will be enclosed in a Future.
For instance, say you have
class HelloWorld extends Controller {
def index = Action { request => Ok("") }
}
then
(new HelloWorld).index().apply(FakeRequest())
still has type
Future[Result]
This leads me to believe that your unit test is indeed appropriate way of testing controllers, that is, Play's documentation is implicitly covering also the case of Action.async.

Scala compiler can't find the unmarshalling implicits in route declaration

I'm trying to build a REST server using this tutorial:
https://spindance.com/reactive-rest-services-akka-http/
However, having reached the "Responding with JSON" section, I've noticed that my code doesn't compile, and I can't make POST requests. This is the error that I'm getting:
Error:(59, 18) could not find implicit value for parameter um: akka.http.scaladsl.unmarshalling.FromRequestUnmarshaller[Health]
entity(as[Health]) { statusReport =>
^
Having looked at other tutorials, I've found out that you need to include an object containing an implicit variable for the class that I'm trying to unmarshall. I did that, and I even imported the httpx library, but I'm still getting this error. My code is given below.
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.model.StatusCodes
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.Route
import akka.stream.ActorMaterializer
import akka.pattern.ask
import akka.util.Timeout
import spray.json._
import DefaultJsonProtocol._
import spray.httpx.SprayJsonSupport.sprayJsonUnmarshaller
import scala.concurrent.duration._
import scala.io.StdIn
object JsonImplicits extends DefaultJsonProtocol {
implicit val healthFormat = jsonFormat2(Health)
}
object MyApplication {
val host = "localhost"
val port = 8080
def main(args: Array[String]): Unit = {
implicit val system = ActorSystem("simple-rest-system")
// Something to do with flows
implicit val materializer = ActorMaterializer()
// A reference to a specific thread pool
// You can configure thread pool options through it
// It is the engine that executes the actors
implicit val executionContext = system.dispatcher
val requestHandler = system.actorOf(RequestHandler.props(), "requestHandler")
//Define the route
val route : Route = {
implicit val timeout = Timeout(20 seconds)
import JsonImplicits._
import spray.httpx.SprayJsonSupport._
path("health") {
get {
onSuccess(requestHandler ? GetHealthRequest) {
case response: HealthResponse =>
complete(StatusCodes.OK, s"Everything is ${response.health.status}!")
case _ =>
complete(StatusCodes.InternalServerError)
}
}
} ~ post {
// Entity extracts the body of the POST request and then converts it into a
// Health object
entity(as[Health]) { statusReport =>
onSuccess(requestHandler ? SetStatusRequest(statusReport)) {
case response: HealthResponse =>
complete(StatusCodes.OK,s"Posted health as ${response.health.status}!")
case _ =>
complete(StatusCodes.InternalServerError)
}
}
}
}
//Start up and listen for requests
val bindingFuture = Http().bindAndHandle(route, host, port)
println(s"Waiting for requests at http://$host:$port/...\nHit RETURN to terminate")
StdIn.readLine()
//Shutdown
bindingFuture.flatMap(_.unbind())
system.terminate()
}
}

Stubbing SOAP requests in Scala

I use scalaxb to generate models and client part of the SOAP interface. For testing I use Betamax, which can also be used in Scala. However, scalaxb uses Netty as a transport, which ignores proxy settings set up by Betamax. How would you cope with this situation?
scalaxb uses cake pattern, so the service is built from 3 parts like in the following example:
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent._
import scala.concurrent.duration._
val service = (new stockquote.StockQuoteSoap12Bindings with
scalaxb.SoapClientsAsync with
scalaxb.DispatchHttpClientsAsync {}).service
val fresponse = service.getQuote(Some("GOOG"))
val response = Await.result(fresponse, 5 seconds)
println(response)
And tests:
import co.freeside.betamax.{TapeMode, Recorder}
import co.freeside.betamax.proxy.jetty.ProxyServer
import dispatch._
import org.scalatest.{Tag, FunSuite}
import scala.concurrent.duration._
import scala.concurrent.{Await, Future}
class StockquoteSpec extends FunSuite with Betamax {
testWithBetamax("stockquote", Some(TapeMode.READ_WRITE))("stockquote") {
val fresponse = service.getQuote(Some("GOOG"))
val response = Await.result(fresponse, 5 seconds)
println(response)
}
}
trait Betamax {
protected def test(testName: String, testTags: Tag*)(testFun: => Unit)
def testWithBetamax(tape: String, mode: Option[TapeMode] = None)(testName: String, testTags: Tag*)(testFun: => Unit) = {
test(testName, testTags: _*) {
val recorder = new Recorder
val proxyServer = new ProxyServer(recorder)
recorder.insertTape(tape)
recorder.getTape.setMode(mode.getOrElse(recorder.getDefaultMode()))
proxyServer.start()
try {
testFun
} finally {
recorder.ejectTape()
proxyServer.stop()
}
}
}
}
Versions:
net.databinder.dispatch 0.11.2
co.freeside.betamax 1.1.2
com.ning.async-http-client 1.8.10
io.netty.netty 3.9.2.Final
It is indeed possible to use proxy with Netty. Although Netty does not read system properties for proxy settings, the settings can be injected using ProxyServerSelector. It is created in build method of AsyncHttpClientConfig:
if (proxyServerSelector == null && useProxySelector) {
proxyServerSelector = ProxyUtils.getJdkDefaultProxyServerSelector();
}
if (proxyServerSelector == null && useProxyProperties) {
proxyServerSelector = ProxyUtils.createProxyServerSelector(System.getProperties());
}
if (proxyServerSelector == null) {
proxyServerSelector = ProxyServerSelector.NO_PROXY_SELECTOR;
}
The only obstacle is that scalaxb uses default config with useProxyProperties=false. You can override it with custom MyDispatchHttpClientsAsync that you can use when creating the service:
val service = (new stockquote.StockQuoteSoap12Bindings with
scalaxb.SoapClientsAsync with
MyDispatchHttpClientsAsync {}).service
And the source code of MyDispatchHttpClientsAsync (the key point is calling setUseProxyProperties(true)):
import com.ning.http.client.providers.netty.NettyAsyncHttpProvider
import com.ning.http.client.{AsyncHttpClientConfig, AsyncHttpClient}
import scalaxb.HttpClientsAsync
/**
* #author miso
*/
trait MyDispatchHttpClientsAsync extends HttpClientsAsync {
lazy val httpClient = new DispatchHttpClient {}
trait DispatchHttpClient extends HttpClient {
import dispatch._, Defaults._
// Keep it lazy. See https://github.com/eed3si9n/scalaxb/pull/279
lazy val http = new Http(new AsyncHttpClient(new NettyAsyncHttpProvider(new AsyncHttpClientConfig.Builder().setUseProxyProperties(true).build())))
// lazy val http = Http.configure(_.setUseProxyProperties(true)) // Maybe later. See https://github.com/eed3si9n/scalaxb/issues/312
def request(in: String, address: java.net.URI, headers: Map[String, String]): concurrent.Future[String] = {
val req = url(address.toString).setBodyEncoding("UTF-8") <:< headers << in
http(req > as.String)
}
}
}

Exception when using elastic4s with elastic-search and spray-routing

I'm trying to write a little REST api using Scala, Spray.io, Elastic4s and ElasticSearch.
My ES instance is running with default parameters, I just changed the parameter network.host to 127.0.0.1.
Here is my spray routing definition
package com.example
import akka.actor.Actor
import spray.routing._
import com.example.core.control.CrudController
class ServiceActor extends Actor with Service {
def actorRefFactory = context
def receive = runRoute(routes)
}
trait Service extends HttpService {
val crudController = new CrudController()
val routes = {
path("ads" / IntNumber) {
id =>
get {
ctx =>
ctx.complete(
crudController.getFromElasticSearch
)
}
}
}
}
My crudController :
package com.example.core.control
import com.example._
import org.elasticsearch.action.search.SearchResponse
import scala.concurrent._
import scala.util.{Success, Failure}
import ExecutionContext.Implicits.global
class CrudController extends elastic4s
{
def getFromElasticSearch : String = {
val something: Future[SearchResponse] = get
something onComplete {
case Success(p) => println(p)
case Failure(t) => println("An error has occured: " + t)
}
"GET received \n"
}
}
And a trait elastic4s who is encapsulating the call to elastic4s
package com.example
import com.sksamuel.elastic4s.ElasticClient
import com.sksamuel.elastic4s.ElasticDsl._
import scala.concurrent._
import org.elasticsearch.action.search.SearchResponse
trait elastic4s {
def get: Future[SearchResponse] = {
val client = ElasticClient.remote("127.0.0.1", 9300)
client execute { search in "ads"->"categories" }
}
}
This code runs well, and gives me this output :
[INFO] [03/26/2014 11:41:50.957] [on-spray-can-akka.actor.default-dispatcher-4] [akka://on-spray-can/user/IO-HTTP/listener-0] Bound to localhost/127.0.0.1:8080
But when a try to access to the route "localhost/ads/8" with my browser, the case Failure is always triggered and I got this error output on my intellij console :
An error has occured: org.elasticsearch.transport.RemoteTransportException: [Skinhead][inet[/127.0.0.1:9300]][search]
(No console output with elasticSearch running on my terminal)
Is this exception related to ElasticSearch, or am I doing wrong with my Future declaration ?
I suppose you should use ElasticClient.local in this case, as specified in elastic4s docs:
https://github.com/sksamuel/elastic4s
To specify settings for the local node you can pass in a settings object like this:
val settings = ImmutableSettings.settingsBuilder()
.put("http.enabled", false)
.put("path.home", "/var/elastic/")
val client = ElasticClient.local(settings.build)