CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-com-typesafe-play--play-ws-2-10

Asynchronous HTTP client for Play Framework with OAuth, OpenID, and SSL/TLS support

Pending
Overview
Eval results
Files

testing.mddocs/

Test Support

Testing utilities for HTTP client operations in Play applications. Play WS provides comprehensive testing support for both unit tests and integration tests with HTTP clients.

Capabilities

WS Test Client

Main testing interface for HTTP client operations.

/**
 * Test client trait providing HTTP testing utilities
 */
trait WsTestClient {
  /**
   * Create a WSRequest for a Play Call (route)
   * @param call Play framework Call (route definition)
   * @param port Implicit test server port
   * @param client Implicit WSClient instance
   * @return WSRequest configured for the test server
   */
  def wsCall(call: Call)(implicit port: Port, client: WSClient): WSRequest
  
  /**
   * Create a WSRequest for a URL relative to test server
   * @param url URL path (will be prefixed with test server base URL)
   * @param port Implicit test server port  
   * @param client Implicit WSClient instance
   * @return WSRequest configured for the test server
   */
  def wsUrl(url: String)(implicit port: Port, client: WSClient): WSRequest
  
  /**
   * Execute test block with a temporary WSClient
   * @param block Test code block that uses WSClient
   * @param port Implicit test server port
   * @return Result of executing the test block
   */
  def withClient[T](block: WSClient => T)(implicit port: Port): T
}

/**
 * WS test client object implementing the trait
 */
object WsTestClient extends WsTestClient

Basic Test Examples

Testing with Play Test Server:

import play.api.test._
import play.api.test.Helpers._
import play.api.libs.ws._
import org.scalatestplus.play._

class MyControllerSpec extends PlaySpec with OneServerPerSuite {
  
  "MyController" should {
    "return JSON data" in {
      implicit val client = app.injector.instanceOf[WSClient]
      
      val response = await(WsTestClient.wsUrl("/api/users").get())
      
      response.status mustBe OK
      response.header("Content-Type") must contain("application/json")
      
      val json = response.json
      (json \ "users").as[Seq[JsObject]] must not be empty
    }
  }
}

Testing with Temporary Client:

import play.api.test._
import play.api.test.Helpers._

class HttpClientSpec extends PlaySpec with OneServerPerSuite {
  
  "HTTP client" should {
    "handle external API calls" in {
      WsTestClient.withClient { client =>
        val response = await(client.url("https://api.example.com/test").get())
        response.status mustBe OK
      }
    }
  }
}

Route-Based Testing

Test your application routes using the wsCall method.

import play.api.routing.sird._
import play.api.test._
import play.api.test.Helpers._
import play.api.mvc._

class RouteTestSpec extends PlaySpec with OneServerPerSuite {
  
  override def fakeApplication(): Application = {
    new GuiceApplicationBuilder()
      .router(Router.from {
        case GET(p"/api/hello") => Action {
          Ok(Json.obj("message" -> "Hello World"))
        }
        case POST(p"/api/echo") => Action(parse.json) { request =>
          Ok(request.body)
        }
      })
      .build()
  }
  
  "API routes" should {
    "handle GET request" in {
      implicit val client = app.injector.instanceOf[WSClient]
      
      // Test using route call
      val call = controllers.routes.ApiController.hello()  // Assuming you have this route
      val response = await(WsTestClient.wsCall(call).get())
      
      response.status mustBe OK
      (response.json \ "message").as[String] mustBe "Hello World"
    }
    
    "handle POST request with JSON" in {
      implicit val client = app.injector.instanceOf[WSClient]
      
      val testData = Json.obj("name" -> "John", "age" -> 30)
      val response = await(WsTestClient.wsUrl("/api/echo").post(testData))
      
      response.status mustBe OK
      response.json mustEqual testData
    }
  }
}

Mock HTTP Server Testing

Create mock servers for testing external API interactions.

import play.core.server.Server
import play.api.routing.sird._
import play.api.mvc._
import play.api.test._
import scala.concurrent.ExecutionContext.Implicits.global

class ExternalApiSpec extends PlaySpec {
  
  "External API client" should {
    "handle mock server responses" in {
      // Create mock server
      Server.withRouter() {
        case GET(p"/api/users/$id") => Action {
          Ok(Json.obj(
            "id" -> id,
            "name" -> s"User $id",
            "email" -> s"user$id@example.com"
          ))
        }
        case POST(p"/api/users") => Action(parse.json) { request =>
          val name = (request.body \ "name").as[String]
          Created(Json.obj(
            "id" -> 123,
            "name" -> name,
            "email" -> s"${name.toLowerCase}@example.com"
          ))
        }
      } { implicit port =>
        WsTestClient.withClient { client =>
          // Test GET request
          val getResponse = await(client.url(s"http://localhost:$port/api/users/1").get())
          getResponse.status mustBe OK
          (getResponse.json \ "name").as[String] mustBe "User 1"
          
          // Test POST request  
          val postData = Json.obj("name" -> "Alice")
          val postResponse = await(client.url(s"http://localhost:$port/api/users").post(postData))
          postResponse.status mustBe CREATED
          (postResponse.json \ "id").as[Int] mustBe 123
        }
      }
    }
  }
}

Authentication Testing

Test OAuth and other authentication mechanisms.

import play.api.test._
import play.api.libs.oauth._

class OAuthTestSpec extends PlaySpec with OneServerPerSuite {
  
  "OAuth integration" should {
    "handle OAuth flow" in {
      implicit val client = app.injector.instanceOf[WSClient]
      
      // Mock OAuth service info
      val serviceInfo = ServiceInfo(
        requestTokenURL = s"http://localhost:$port/oauth/request_token",
        accessTokenURL = s"http://localhost:$port/oauth/access_token",
        authorizationURL = s"http://localhost:$port/oauth/authorize",
        key = ConsumerKey("test_key", "test_secret")
      )
      
      val oauth = OAuth(serviceInfo)
      
      // Test would require mock OAuth endpoints
      // This is a simplified example structure
      oauth.retrieveRequestToken("http://callback") match {
        case Right(token) =>
          token.token must not be empty
        case Left(error) =>
          fail(s"OAuth request failed: $error")
      }
    }
  }
}

Error Handling Testing

Test error conditions and edge cases.

class ErrorHandlingSpec extends PlaySpec with OneServerPerSuite {
  
  "Error handling" should {
    "handle 404 responses" in {
      implicit val client = app.injector.instanceOf[WSClient]
      
      val response = await(WsTestClient.wsUrl("/nonexistent").get())
      response.status mustBe NOT_FOUND
    }
    
    "handle connection timeouts" in {
      WsTestClient.withClient { client =>
        val request = client.url("http://10.255.255.1:12345/timeout")
          .withRequestTimeout(1000)  // 1 second timeout
        
        // This should timeout quickly
        intercept[java.util.concurrent.TimeoutException] {
          await(request.get())
        }
      }
    }
    
    "handle invalid JSON responses gracefully" in {
      Server.withRouter() {
        case GET(p"/invalid-json") => Action {
          Ok("{ invalid json }")
        }
      } { implicit port =>
        WsTestClient.withClient { client =>
          val response = await(client.url(s"http://localhost:$port/invalid-json").get())
          response.status mustBe OK
          
          intercept[com.fasterxml.jackson.core.JsonParseException] {
            response.json
          }
        }
      }
    }
  }
}

Streaming Response Testing

Test streaming responses and large data handling.

import play.api.libs.iteratee._
import akka.util.ByteString

class StreamingSpec extends PlaySpec with OneServerPerSuite {
  
  "Streaming responses" should {
    "handle large responses" in {
      Server.withRouter() {
        case GET(p"/large-data") => Action {
          val largeData = "x" * 1000000  // 1MB of data
          Ok(largeData)
        }
      } { implicit port =>
        WsTestClient.withClient { client =>
          val response = await(client.url(s"http://localhost:$port/large-data").stream())
          
          response match {
            case (headers, body) =>
              headers.status mustBe OK
              
              // Consume the stream
              val consumed = body |>>> Iteratee.consume[Array[Byte]]()
              val data = await(consumed)
              data.length mustBe 1000000
          }
        }
      }
    }
  }
}

Custom Test Configurations

Create custom WS client configurations for testing.

import play.api.libs.ws.ning._
import scala.concurrent.duration._

class CustomConfigSpec extends PlaySpec {
  
  "Custom WS configuration" should {
    "use test-specific timeouts" in {
      val testConfig = NingWSClientConfig(
        wsClientConfig = WSClientConfig(
          connectionTimeout = 5.seconds,
          requestTimeout = 10.seconds,
          followRedirects = false
        )
      )
      
      val client = NingWSClient(testConfig)
      
      try {
        // Use client for testing with custom configuration
        val response = await(client.url("https://httpbin.org/delay/2").get())
        response.status mustBe OK
      } finally {
        client.close()
      }
    }
  }
}

Integration Test Setup

Complete setup for integration testing with WS client.

import org.scalatestplus.play._
import play.api.test._
import play.api.inject.guice.GuiceApplicationBuilder

class IntegrationTestSpec extends PlaySpec with OneServerPerSuite with OneBrowserPerSuite {
  
  override def fakeApplication(): Application = {
    new GuiceApplicationBuilder()
      .configure(
        "play.ws.timeout.connection" -> "10s",
        "play.ws.timeout.idle" -> "10s",
        "play.ws.timeout.request" -> "10s"
      )
      .build()
  }
  
  "Full application" should {
    "handle complete user workflow" in {
      implicit val client = app.injector.instanceOf[WSClient]
      
      // Step 1: Create user
      val createUser = Json.obj(
        "name" -> "Test User",
        "email" -> "test@example.com"
      )
      
      val createResponse = await(WsTestClient.wsUrl("/api/users").post(createUser))
      createResponse.status mustBe CREATED
      
      val userId = (createResponse.json \ "id").as[Long]
      
      // Step 2: Retrieve user
      val getResponse = await(WsTestClient.wsUrl(s"/api/users/$userId").get())
      getResponse.status mustBe OK
      
      val user = getResponse.json
      (user \ "name").as[String] mustBe "Test User"
      (user \ "email").as[String] mustBe "test@example.com"
      
      // Step 3: Update user
      val updateData = Json.obj("name" -> "Updated User")
      val updateResponse = await(WsTestClient.wsUrl(s"/api/users/$userId").put(updateData))
      updateResponse.status mustBe OK
      
      // Step 4: Verify update
      val verifyResponse = await(WsTestClient.wsUrl(s"/api/users/$userId").get())
      (verifyResponse.json \ "name").as[String] mustBe "Updated User"
      
      // Step 5: Delete user
      val deleteResponse = await(WsTestClient.wsUrl(s"/api/users/$userId").delete())
      deleteResponse.status mustBe NO_CONTENT
    }
  }
}

Performance Testing

Basic performance testing with WS client.

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global

class PerformanceSpec extends PlaySpec with OneServerPerSuite {
  
  "Performance tests" should {
    "handle concurrent requests" in {
      implicit val client = app.injector.instanceOf[WSClient]
      
      val concurrentRequests = 10
      val requests = (1 to concurrentRequests).map { i =>
        WsTestClient.wsUrl(s"/api/users/$i").get()
      }
      
      val startTime = System.currentTimeMillis()
      val responses = await(Future.sequence(requests))
      val endTime = System.currentTimeMillis()
      
      responses.length mustBe concurrentRequests
      responses.foreach(_.status mustBe OK)
      
      val totalTime = endTime - startTime
      println(s"$concurrentRequests requests completed in ${totalTime}ms")
      
      // Assert reasonable performance (adjust threshold as needed)
      totalTime must be < 5000L  // Less than 5 seconds
    }
  }
}

Install with Tessl CLI

npx tessl i tessl/maven-com-typesafe-play--play-ws-2-10

docs

http-client.md

index.md

ning.md

oauth.md

openid.md

ssl.md

testing.md

tile.json