0
# Test Support
1
2
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.
3
4
## Capabilities
5
6
### WS Test Client
7
8
Main testing interface for HTTP client operations.
9
10
```scala { .api }
11
/**
12
* Test client trait providing HTTP testing utilities
13
*/
14
trait WsTestClient {
15
/**
16
* Create a WSRequest for a Play Call (route)
17
* @param call Play framework Call (route definition)
18
* @param port Implicit test server port
19
* @param client Implicit WSClient instance
20
* @return WSRequest configured for the test server
21
*/
22
def wsCall(call: Call)(implicit port: Port, client: WSClient): WSRequest
23
24
/**
25
* Create a WSRequest for a URL relative to test server
26
* @param url URL path (will be prefixed with test server base URL)
27
* @param port Implicit test server port
28
* @param client Implicit WSClient instance
29
* @return WSRequest configured for the test server
30
*/
31
def wsUrl(url: String)(implicit port: Port, client: WSClient): WSRequest
32
33
/**
34
* Execute test block with a temporary WSClient
35
* @param block Test code block that uses WSClient
36
* @param port Implicit test server port
37
* @return Result of executing the test block
38
*/
39
def withClient[T](block: WSClient => T)(implicit port: Port): T
40
}
41
42
/**
43
* WS test client object implementing the trait
44
*/
45
object WsTestClient extends WsTestClient
46
```
47
48
### Basic Test Examples
49
50
**Testing with Play Test Server:**
51
52
```scala
53
import play.api.test._
54
import play.api.test.Helpers._
55
import play.api.libs.ws._
56
import org.scalatestplus.play._
57
58
class MyControllerSpec extends PlaySpec with OneServerPerSuite {
59
60
"MyController" should {
61
"return JSON data" in {
62
implicit val client = app.injector.instanceOf[WSClient]
63
64
val response = await(WsTestClient.wsUrl("/api/users").get())
65
66
response.status mustBe OK
67
response.header("Content-Type") must contain("application/json")
68
69
val json = response.json
70
(json \ "users").as[Seq[JsObject]] must not be empty
71
}
72
}
73
}
74
```
75
76
**Testing with Temporary Client:**
77
78
```scala
79
import play.api.test._
80
import play.api.test.Helpers._
81
82
class HttpClientSpec extends PlaySpec with OneServerPerSuite {
83
84
"HTTP client" should {
85
"handle external API calls" in {
86
WsTestClient.withClient { client =>
87
val response = await(client.url("https://api.example.com/test").get())
88
response.status mustBe OK
89
}
90
}
91
}
92
}
93
```
94
95
### Route-Based Testing
96
97
Test your application routes using the `wsCall` method.
98
99
```scala
100
import play.api.routing.sird._
101
import play.api.test._
102
import play.api.test.Helpers._
103
import play.api.mvc._
104
105
class RouteTestSpec extends PlaySpec with OneServerPerSuite {
106
107
override def fakeApplication(): Application = {
108
new GuiceApplicationBuilder()
109
.router(Router.from {
110
case GET(p"/api/hello") => Action {
111
Ok(Json.obj("message" -> "Hello World"))
112
}
113
case POST(p"/api/echo") => Action(parse.json) { request =>
114
Ok(request.body)
115
}
116
})
117
.build()
118
}
119
120
"API routes" should {
121
"handle GET request" in {
122
implicit val client = app.injector.instanceOf[WSClient]
123
124
// Test using route call
125
val call = controllers.routes.ApiController.hello() // Assuming you have this route
126
val response = await(WsTestClient.wsCall(call).get())
127
128
response.status mustBe OK
129
(response.json \ "message").as[String] mustBe "Hello World"
130
}
131
132
"handle POST request with JSON" in {
133
implicit val client = app.injector.instanceOf[WSClient]
134
135
val testData = Json.obj("name" -> "John", "age" -> 30)
136
val response = await(WsTestClient.wsUrl("/api/echo").post(testData))
137
138
response.status mustBe OK
139
response.json mustEqual testData
140
}
141
}
142
}
143
```
144
145
### Mock HTTP Server Testing
146
147
Create mock servers for testing external API interactions.
148
149
```scala
150
import play.core.server.Server
151
import play.api.routing.sird._
152
import play.api.mvc._
153
import play.api.test._
154
import scala.concurrent.ExecutionContext.Implicits.global
155
156
class ExternalApiSpec extends PlaySpec {
157
158
"External API client" should {
159
"handle mock server responses" in {
160
// Create mock server
161
Server.withRouter() {
162
case GET(p"/api/users/$id") => Action {
163
Ok(Json.obj(
164
"id" -> id,
165
"name" -> s"User $id",
166
"email" -> s"user$id@example.com"
167
))
168
}
169
case POST(p"/api/users") => Action(parse.json) { request =>
170
val name = (request.body \ "name").as[String]
171
Created(Json.obj(
172
"id" -> 123,
173
"name" -> name,
174
"email" -> s"${name.toLowerCase}@example.com"
175
))
176
}
177
} { implicit port =>
178
WsTestClient.withClient { client =>
179
// Test GET request
180
val getResponse = await(client.url(s"http://localhost:$port/api/users/1").get())
181
getResponse.status mustBe OK
182
(getResponse.json \ "name").as[String] mustBe "User 1"
183
184
// Test POST request
185
val postData = Json.obj("name" -> "Alice")
186
val postResponse = await(client.url(s"http://localhost:$port/api/users").post(postData))
187
postResponse.status mustBe CREATED
188
(postResponse.json \ "id").as[Int] mustBe 123
189
}
190
}
191
}
192
}
193
}
194
```
195
196
### Authentication Testing
197
198
Test OAuth and other authentication mechanisms.
199
200
```scala
201
import play.api.test._
202
import play.api.libs.oauth._
203
204
class OAuthTestSpec extends PlaySpec with OneServerPerSuite {
205
206
"OAuth integration" should {
207
"handle OAuth flow" in {
208
implicit val client = app.injector.instanceOf[WSClient]
209
210
// Mock OAuth service info
211
val serviceInfo = ServiceInfo(
212
requestTokenURL = s"http://localhost:$port/oauth/request_token",
213
accessTokenURL = s"http://localhost:$port/oauth/access_token",
214
authorizationURL = s"http://localhost:$port/oauth/authorize",
215
key = ConsumerKey("test_key", "test_secret")
216
)
217
218
val oauth = OAuth(serviceInfo)
219
220
// Test would require mock OAuth endpoints
221
// This is a simplified example structure
222
oauth.retrieveRequestToken("http://callback") match {
223
case Right(token) =>
224
token.token must not be empty
225
case Left(error) =>
226
fail(s"OAuth request failed: $error")
227
}
228
}
229
}
230
}
231
```
232
233
### Error Handling Testing
234
235
Test error conditions and edge cases.
236
237
```scala
238
class ErrorHandlingSpec extends PlaySpec with OneServerPerSuite {
239
240
"Error handling" should {
241
"handle 404 responses" in {
242
implicit val client = app.injector.instanceOf[WSClient]
243
244
val response = await(WsTestClient.wsUrl("/nonexistent").get())
245
response.status mustBe NOT_FOUND
246
}
247
248
"handle connection timeouts" in {
249
WsTestClient.withClient { client =>
250
val request = client.url("http://10.255.255.1:12345/timeout")
251
.withRequestTimeout(1000) // 1 second timeout
252
253
// This should timeout quickly
254
intercept[java.util.concurrent.TimeoutException] {
255
await(request.get())
256
}
257
}
258
}
259
260
"handle invalid JSON responses gracefully" in {
261
Server.withRouter() {
262
case GET(p"/invalid-json") => Action {
263
Ok("{ invalid json }")
264
}
265
} { implicit port =>
266
WsTestClient.withClient { client =>
267
val response = await(client.url(s"http://localhost:$port/invalid-json").get())
268
response.status mustBe OK
269
270
intercept[com.fasterxml.jackson.core.JsonParseException] {
271
response.json
272
}
273
}
274
}
275
}
276
}
277
}
278
```
279
280
### Streaming Response Testing
281
282
Test streaming responses and large data handling.
283
284
```scala
285
import play.api.libs.iteratee._
286
import akka.util.ByteString
287
288
class StreamingSpec extends PlaySpec with OneServerPerSuite {
289
290
"Streaming responses" should {
291
"handle large responses" in {
292
Server.withRouter() {
293
case GET(p"/large-data") => Action {
294
val largeData = "x" * 1000000 // 1MB of data
295
Ok(largeData)
296
}
297
} { implicit port =>
298
WsTestClient.withClient { client =>
299
val response = await(client.url(s"http://localhost:$port/large-data").stream())
300
301
response match {
302
case (headers, body) =>
303
headers.status mustBe OK
304
305
// Consume the stream
306
val consumed = body |>>> Iteratee.consume[Array[Byte]]()
307
val data = await(consumed)
308
data.length mustBe 1000000
309
}
310
}
311
}
312
}
313
}
314
}
315
```
316
317
### Custom Test Configurations
318
319
Create custom WS client configurations for testing.
320
321
```scala
322
import play.api.libs.ws.ning._
323
import scala.concurrent.duration._
324
325
class CustomConfigSpec extends PlaySpec {
326
327
"Custom WS configuration" should {
328
"use test-specific timeouts" in {
329
val testConfig = NingWSClientConfig(
330
wsClientConfig = WSClientConfig(
331
connectionTimeout = 5.seconds,
332
requestTimeout = 10.seconds,
333
followRedirects = false
334
)
335
)
336
337
val client = NingWSClient(testConfig)
338
339
try {
340
// Use client for testing with custom configuration
341
val response = await(client.url("https://httpbin.org/delay/2").get())
342
response.status mustBe OK
343
} finally {
344
client.close()
345
}
346
}
347
}
348
}
349
```
350
351
### Integration Test Setup
352
353
Complete setup for integration testing with WS client.
354
355
```scala
356
import org.scalatestplus.play._
357
import play.api.test._
358
import play.api.inject.guice.GuiceApplicationBuilder
359
360
class IntegrationTestSpec extends PlaySpec with OneServerPerSuite with OneBrowserPerSuite {
361
362
override def fakeApplication(): Application = {
363
new GuiceApplicationBuilder()
364
.configure(
365
"play.ws.timeout.connection" -> "10s",
366
"play.ws.timeout.idle" -> "10s",
367
"play.ws.timeout.request" -> "10s"
368
)
369
.build()
370
}
371
372
"Full application" should {
373
"handle complete user workflow" in {
374
implicit val client = app.injector.instanceOf[WSClient]
375
376
// Step 1: Create user
377
val createUser = Json.obj(
378
"name" -> "Test User",
379
"email" -> "test@example.com"
380
)
381
382
val createResponse = await(WsTestClient.wsUrl("/api/users").post(createUser))
383
createResponse.status mustBe CREATED
384
385
val userId = (createResponse.json \ "id").as[Long]
386
387
// Step 2: Retrieve user
388
val getResponse = await(WsTestClient.wsUrl(s"/api/users/$userId").get())
389
getResponse.status mustBe OK
390
391
val user = getResponse.json
392
(user \ "name").as[String] mustBe "Test User"
393
(user \ "email").as[String] mustBe "test@example.com"
394
395
// Step 3: Update user
396
val updateData = Json.obj("name" -> "Updated User")
397
val updateResponse = await(WsTestClient.wsUrl(s"/api/users/$userId").put(updateData))
398
updateResponse.status mustBe OK
399
400
// Step 4: Verify update
401
val verifyResponse = await(WsTestClient.wsUrl(s"/api/users/$userId").get())
402
(verifyResponse.json \ "name").as[String] mustBe "Updated User"
403
404
// Step 5: Delete user
405
val deleteResponse = await(WsTestClient.wsUrl(s"/api/users/$userId").delete())
406
deleteResponse.status mustBe NO_CONTENT
407
}
408
}
409
}
410
```
411
412
### Performance Testing
413
414
Basic performance testing with WS client.
415
416
```scala
417
import scala.concurrent.Future
418
import scala.concurrent.ExecutionContext.Implicits.global
419
420
class PerformanceSpec extends PlaySpec with OneServerPerSuite {
421
422
"Performance tests" should {
423
"handle concurrent requests" in {
424
implicit val client = app.injector.instanceOf[WSClient]
425
426
val concurrentRequests = 10
427
val requests = (1 to concurrentRequests).map { i =>
428
WsTestClient.wsUrl(s"/api/users/$i").get()
429
}
430
431
val startTime = System.currentTimeMillis()
432
val responses = await(Future.sequence(requests))
433
val endTime = System.currentTimeMillis()
434
435
responses.length mustBe concurrentRequests
436
responses.foreach(_.status mustBe OK)
437
438
val totalTime = endTime - startTime
439
println(s"$concurrentRequests requests completed in ${totalTime}ms")
440
441
// Assert reasonable performance (adjust threshold as needed)
442
totalTime must be < 5000L // Less than 5 seconds
443
}
444
}
445
}
446
```