19
19
import java .nio .charset .Charset ;
20
20
import java .time .Duration ;
21
21
import java .time .ZonedDateTime ;
22
+ import java .util .Arrays ;
22
23
import java .util .List ;
23
24
import java .util .Map ;
24
25
import java .util .Optional ;
25
26
import java .util .concurrent .ConcurrentHashMap ;
26
27
import java .util .concurrent .atomic .AtomicLong ;
27
28
import java .util .function .Consumer ;
28
29
import java .util .function .Function ;
30
+ import java .util .stream .Collectors ;
29
31
30
32
import org .reactivestreams .Publisher ;
31
33
import reactor .core .publisher .Flux ;
34
36
import org .springframework .core .ResolvableType ;
35
37
import org .springframework .core .io .buffer .DataBuffer ;
36
38
import org .springframework .http .HttpHeaders ;
39
+ import org .springframework .http .HttpMethod ;
37
40
import org .springframework .http .MediaType ;
38
41
import org .springframework .http .client .reactive .ClientHttpConnector ;
39
42
import org .springframework .http .client .reactive .ClientHttpRequest ;
40
- import org .springframework .test .util .AssertionErrors ;
41
43
import org .springframework .util .Assert ;
42
44
import org .springframework .util .MultiValueMap ;
43
45
import org .springframework .web .reactive .function .BodyInserter ;
46
48
import org .springframework .web .reactive .function .client .WebClient ;
47
49
import org .springframework .web .util .UriBuilder ;
48
50
51
+ import static org .springframework .test .util .AssertionErrors .assertEquals ;
52
+ import static org .springframework .test .util .AssertionErrors .assertTrue ;
53
+ import static org .springframework .test .util .AssertionErrors .fail ;
49
54
import static org .springframework .web .reactive .function .BodyExtractors .toDataBuffers ;
50
55
import static org .springframework .web .reactive .function .BodyExtractors .toFlux ;
51
56
import static org .springframework .web .reactive .function .BodyExtractors .toMono ;
@@ -242,88 +247,301 @@ public DefaultHeaderSpec ifNoneMatch(String... ifNoneMatches) {
242
247
243
248
@ Override
244
249
public ResponseSpec exchange () {
245
- return new DefaultResponseSpec ( this . requestId , this .headerSpec .exchange ());
250
+ return createResponseSpec ( this .headerSpec .exchange ());
246
251
}
247
252
248
253
@ Override
249
254
public <T > ResponseSpec exchange (BodyInserter <T , ? super ClientHttpRequest > inserter ) {
250
- return new DefaultResponseSpec ( this . requestId , this .headerSpec .exchange (inserter ));
255
+ return createResponseSpec ( this .headerSpec .exchange (inserter ));
251
256
}
252
257
253
258
@ Override
254
259
public <T , S extends Publisher <T >> ResponseSpec exchange (S publisher , Class <T > elementClass ) {
255
- return new DefaultResponseSpec ( this . requestId , this .headerSpec .exchange (publisher , elementClass ));
260
+ return createResponseSpec ( this .headerSpec .exchange (publisher , elementClass ));
256
261
}
262
+
263
+ protected DefaultResponseSpec createResponseSpec (Mono <ClientResponse > responseMono ) {
264
+ ClientResponse response = responseMono .block (getTimeout ());
265
+ WiretapConnector .Info info = connectorListener .retrieveRequest (this .requestId );
266
+ HttpMethod method = info .getMethod ();
267
+ URI url = info .getUrl ();
268
+ HttpHeaders headers = info .getRequestHeaders ();
269
+ ExchangeResult <Flux <DataBuffer >> result = ExchangeResult .fromResponse (method , url , headers , response );
270
+ return new DefaultResponseSpec (result , response );
271
+ }
272
+
257
273
}
258
274
259
- private class DefaultResponseSpec implements ResponseSpec {
275
+ private abstract class ResponseSpecSupport {
260
276
261
- private final String requestId ;
277
+ private final ExchangeResult <Flux <DataBuffer >> exchangeResult ;
278
+
279
+ private final ClientResponse response ;
280
+
281
+
282
+ public ResponseSpecSupport (ExchangeResult <Flux <DataBuffer >> result , ClientResponse response ) {
283
+ this .exchangeResult = result ;
284
+ this .response = response ;
285
+ }
262
286
263
- private final Mono <ClientResponse > responseMono ;
287
+ public ResponseSpecSupport (ResponseSpecSupport responseSpec ) {
288
+ this .exchangeResult = responseSpec .getExchangeResult ();
289
+ this .response = responseSpec .getResponse ();
290
+ }
291
+
292
+
293
+ protected ExchangeResult <Flux <DataBuffer >> getExchangeResult () {
294
+ return this .exchangeResult ;
295
+ }
264
296
297
+ protected ClientResponse getResponse () {
298
+ return this .response ;
299
+ }
300
+
301
+ protected HttpHeaders getResponseHeaders () {
302
+ return getExchangeResult ().getResponseHeaders ();
303
+ }
265
304
266
- public DefaultResponseSpec (String requestId , Mono <ClientResponse > responseMono ) {
267
- this .requestId = requestId ;
268
- this .responseMono = responseMono ;
305
+ protected <T > ExchangeResult <T > createResultWithDecodedBody (T body ) {
306
+ return ExchangeResult .withDecodedBody (this .exchangeResult , body );
307
+ }
308
+
309
+ }
310
+
311
+ private class DefaultResponseSpec extends ResponseSpecSupport implements ResponseSpec {
312
+
313
+
314
+ public DefaultResponseSpec (ExchangeResult <Flux <DataBuffer >> result , ClientResponse response ) {
315
+ super (result , response );
269
316
}
270
317
271
318
272
319
@ Override
273
- public < T > ExchangeResult < T > decodeEntity ( Class < T > entityClass ) {
274
- return decodeEntity ( ResolvableType . forClass ( entityClass ) );
320
+ public StatusAssertions expectStatus ( ) {
321
+ return new StatusAssertions ( getResponse (). statusCode (), this );
275
322
}
276
323
277
324
@ Override
278
- public < T > ExchangeResult < List < T >> decodeAndCollect ( Class < T > elementClass ) {
279
- return decodeAndCollect ( ResolvableType . forClass ( elementClass ) );
325
+ public HeaderAssertions expectHeader ( ) {
326
+ return new HeaderAssertions ( getResponseHeaders (), this );
280
327
}
281
328
282
329
@ Override
283
- public < T > ExchangeResult < Flux < T >> decodeFlux ( Class < T > elementClass ) {
284
- return decodeFlux ( ResolvableType . forClass ( elementClass ) );
330
+ public BodySpec expectBody ( ) {
331
+ return new DefaultBodySpec ( this );
285
332
}
286
333
287
334
@ Override
288
- public <T > ExchangeResult <T > decodeEntity (ResolvableType elementType ) {
289
- return this .responseMono .then (response -> {
290
- Mono <T > entityMono = response .body (toMono (elementType ));
291
- return entityMono .map (entity -> createTestExchange (entity , response ));
292
- }).block (getTimeout ());
335
+ public ElementBodySpec expectBody (Class <?> elementType ) {
336
+ return expectBody (ResolvableType .forClass (elementType ));
293
337
}
294
338
295
339
@ Override
296
- public <T > ExchangeResult <List <T >> decodeAndCollect (ResolvableType elementType ) {
297
- return this .responseMono .then (response -> {
298
- Flux <T > entityFlux = response .body (toFlux (elementType ));
299
- return entityFlux .collectList ().map (list -> createTestExchange (list , response ));
300
- }).block (getTimeout ());
340
+ public ElementBodySpec expectBody (ResolvableType elementType ) {
341
+ return new DefaultElementBodySpec (this , elementType );
301
342
}
302
343
303
344
@ Override
304
- public <T > ExchangeResult <Flux <T >> decodeFlux (ResolvableType elementType ) {
305
- return this .responseMono .map (response -> {
306
- Flux <T > entityFlux = response .body (toFlux (elementType ));
307
- return createTestExchange (entityFlux , response );
308
- }).block (getTimeout ());
345
+ public ResponseSpec consumeWith (Consumer <ExchangeResult <Flux <DataBuffer >>> consumer ) {
346
+ consumer .accept (getExchangeResult ());
347
+ return this ;
309
348
}
310
349
311
350
@ Override
312
- public ExchangeResult <Void > expectNoBody () {
313
- return this .responseMono .map (response -> {
314
- DataBuffer buffer = response .body (toDataBuffers ()).blockFirst (getTimeout ());
315
- AssertionErrors .assertTrue ("Expected empty body" , buffer == null );
316
- ExchangeResult <Void > exchange = createTestExchange (null , response );
317
- return exchange ;
318
- }).block (getTimeout ());
351
+ public ExchangeResult <Flux <DataBuffer >> returnResult () {
352
+ return getExchangeResult ();
353
+ }
354
+ }
355
+
356
+ private class DefaultBodySpec extends ResponseSpecSupport implements BodySpec {
357
+
358
+
359
+ public DefaultBodySpec (ResponseSpecSupport responseSpec ) {
360
+ super (responseSpec );
361
+ }
362
+
363
+
364
+ @ Override
365
+ public ExchangeResult <Void > isEmpty () {
366
+ DataBuffer buffer = getResponse ().body (toDataBuffers ()).blockFirst (getTimeout ());
367
+ assertTrue ("Expected empty body" , buffer == null );
368
+ return createResultWithDecodedBody (null );
319
369
}
320
370
321
- private <T > ExchangeResult <T > createTestExchange (T body , ClientResponse response ) {
322
- WiretapConnector .Info wiretapInfo = connectorListener .retrieveRequest (requestId );
323
- ClientHttpRequest request = wiretapInfo .getRequest ();
324
- return new ExchangeResult <T >(
325
- request .getMethod (), request .getURI (), request .getHeaders (),
326
- response .statusCode (), response .headers ().asHttpHeaders (), body );
371
+ @ Override
372
+ public MapBodySpec map (Class <?> keyType , Class <?> valueType ) {
373
+ return map (ResolvableType .forClass (keyType ), ResolvableType .forClass (valueType ));
374
+ }
375
+
376
+ @ Override
377
+ public MapBodySpec map (ResolvableType keyType , ResolvableType valueType ) {
378
+ return new DefaultMapBodySpec (this , keyType , valueType );
379
+ }
380
+ }
381
+
382
+ private class DefaultMapBodySpec extends ResponseSpecSupport implements MapBodySpec {
383
+
384
+ private final Map <?, ?> body ;
385
+
386
+
387
+ public DefaultMapBodySpec (ResponseSpecSupport spec , ResolvableType keyType , ResolvableType valueType ) {
388
+ super (spec );
389
+ ResolvableType mapType = ResolvableType .forClassWithGenerics (Map .class , keyType , valueType );
390
+ this .body = (Map <?, ?>) spec .getResponse ().body (toMono (mapType )).block (getTimeout ());
391
+ }
392
+
393
+
394
+ @ Override
395
+ public <K , V > ExchangeResult <Map <K , V >> isEqualTo (Map <K , V > expected ) {
396
+ return returnResult ();
397
+ }
398
+
399
+ @ Override
400
+ public MapBodySpec hasSize (int size ) {
401
+ assertEquals ("Response body map size" , size , this .body .size ());
402
+ return this ;
403
+ }
404
+
405
+ @ Override
406
+ public MapBodySpec contains (Object key , Object value ) {
407
+ assertEquals ("Response body map value for key " + key , value , this .body .get (key ));
408
+ return this ;
409
+ }
410
+
411
+ @ Override
412
+ public MapBodySpec containsKeys (Object ... keys ) {
413
+ List <Object > missing = Arrays .stream (keys )
414
+ .filter (key -> !this .body .containsKey (key ))
415
+ .collect (Collectors .toList ());
416
+ if (!missing .isEmpty ()) {
417
+ fail ("Response body map does not contain keys " + Arrays .toString (keys ));
418
+ }
419
+ return this ;
420
+ }
421
+
422
+ @ Override
423
+ public MapBodySpec containsValues (Object ... values ) {
424
+ List <Object > missing = Arrays .stream (values )
425
+ .filter (value -> !this .body .containsValue (value ))
426
+ .collect (Collectors .toList ());
427
+ if (!missing .isEmpty ()) {
428
+ fail ("Response body map does not contain values " + Arrays .toString (values ));
429
+ }
430
+ return this ;
431
+ }
432
+
433
+ @ Override
434
+ @ SuppressWarnings ("unchecked" )
435
+ public <K , V > ExchangeResult <Map <K , V >> returnResult () {
436
+ return createResultWithDecodedBody ((Map <K , V >) this .body );
437
+ }
438
+ }
439
+
440
+ private class DefaultElementBodySpec extends ResponseSpecSupport implements ElementBodySpec {
441
+
442
+ private final ResolvableType elementType ;
443
+
444
+
445
+ public DefaultElementBodySpec (ResponseSpecSupport spec , ResolvableType elementType ) {
446
+ super (spec );
447
+ this .elementType = elementType ;
448
+ }
449
+
450
+
451
+ @ Override
452
+ public SingleValueBodySpec value () {
453
+ return new DefaultSingleValueBodySpec (this , this .elementType );
454
+ }
455
+
456
+ @ Override
457
+ public ListBodySpec list () {
458
+ return new DefaultListBodySpec (this , this .elementType , -1 );
459
+ }
460
+
461
+ @ Override
462
+ public ListBodySpec list (int elementCount ) {
463
+ return new DefaultListBodySpec (this , this .elementType , elementCount );
464
+ }
465
+
466
+ @ Override
467
+ public <T > ExchangeResult <Flux <T >> returnResult () {
468
+ Flux <T > flux = getResponse ().body (toFlux (this .elementType ));
469
+ return createResultWithDecodedBody (flux );
470
+ }
471
+ }
472
+
473
+ private class DefaultSingleValueBodySpec extends ResponseSpecSupport
474
+ implements SingleValueBodySpec {
475
+
476
+ private final Object body ;
477
+
478
+
479
+ public DefaultSingleValueBodySpec (ResponseSpecSupport spec , ResolvableType elementType ) {
480
+ super (spec );
481
+ this .body = getResponse ().body (toMono (elementType )).block (getTimeout ());
482
+ }
483
+
484
+
485
+ @ Override
486
+ public <T > ExchangeResult <T > isEqualTo (Object expected ) {
487
+ assertEquals ("Response body" , expected , this .body );
488
+ return returnResult ();
489
+ }
490
+
491
+ @ Override
492
+ @ SuppressWarnings ("unchecked" )
493
+ public <T > ExchangeResult <T > returnResult () {
494
+ return createResultWithDecodedBody ((T ) this .body );
495
+ }
496
+ }
497
+
498
+ private class DefaultListBodySpec extends ResponseSpecSupport
499
+ implements ListBodySpec {
500
+
501
+ private final List <?> body ;
502
+
503
+
504
+ public DefaultListBodySpec (ResponseSpecSupport spec , ResolvableType elementType , int elementCount ) {
505
+ super (spec );
506
+ Flux <?> flux = getResponse ().body (toFlux (elementType ));
507
+ if (elementCount >= 0 ) {
508
+ flux = flux .take (elementCount );
509
+ }
510
+ this .body = flux .collectList ().block (getTimeout ());
511
+ }
512
+
513
+
514
+ @ Override
515
+ public <T > ExchangeResult <List <T >> isEqualTo (List <T > expected ) {
516
+ assertEquals ("Response body" , expected , this .body );
517
+ return returnResult ();
518
+ }
519
+
520
+ @ Override
521
+ public ListBodySpec hasSize (int size ) {
522
+ return this ;
523
+ }
524
+
525
+ @ Override
526
+ public ListBodySpec contains (Object ... elements ) {
527
+ List <Object > elementList = Arrays .asList (elements );
528
+ String message = "Response body does not contain " + elementList ;
529
+ assertTrue (message , this .body .containsAll (elementList ));
530
+ return this ;
531
+ }
532
+
533
+ @ Override
534
+ public ListBodySpec doesNotContain (Object ... elements ) {
535
+ List <Object > elementList = Arrays .asList (elements );
536
+ String message = "Response body should have contained " + elementList ;
537
+ assertTrue (message , !this .body .containsAll (Arrays .asList (elements )));
538
+ return this ;
539
+ }
540
+
541
+ @ Override
542
+ @ SuppressWarnings ("unchecked" )
543
+ public <T > ExchangeResult <List <T >> returnResult () {
544
+ return createResultWithDecodedBody ((List <T >) this .body );
327
545
}
328
546
}
329
547
@@ -346,7 +564,7 @@ public String registerRequestId(WebClient.HeaderSpec headerSpec) {
346
564
347
565
@ Override
348
566
public void accept (WiretapConnector .Info info ) {
349
- Optional .ofNullable (info .getRequest (). getHeaders ().getFirst (REQUEST_ID_HEADER_NAME ))
567
+ Optional .ofNullable (info .getRequestHeaders ().getFirst (REQUEST_ID_HEADER_NAME ))
350
568
.ifPresent (id -> this .exchanges .put (id , info ));
351
569
}
352
570
0 commit comments