Skip to content

Commit 2435820

Browse files
committed
Assertion errors with request and response details
Issue: SPR-15249
1 parent d1a64e1 commit 2435820

File tree

9 files changed

+653
-142
lines changed

9 files changed

+653
-142
lines changed

spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClient.java

Lines changed: 70 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -300,29 +300,31 @@ public <T> FluxExchangeResult<T> decodeBody(ResolvableType elementType) {
300300
}
301301

302302
public EntityExchangeResult<Void> consumeEmpty() {
303-
DataBuffer buffer = this.response.body(toDataBuffers()).blockFirst(getTimeout());
304-
assertTrue("Expected empty body", buffer == null);
303+
assertWithDiagnostics(() -> {
304+
DataBuffer buffer = this.response.body(toDataBuffers()).blockFirst(getTimeout());
305+
assertTrue("Expected empty body", buffer == null);
306+
});
305307
return new EntityExchangeResult<>(this, null);
306308
}
307309
}
308310

309311
private class DefaultResponseSpec implements ResponseSpec {
310312

311-
private final UndecodedExchangeResult exchangeResult;
313+
private final UndecodedExchangeResult result;
312314

313315

314316
public DefaultResponseSpec(ClientHttpRequest httpRequest, ClientResponse response) {
315-
this.exchangeResult = new UndecodedExchangeResult(httpRequest, response);
317+
this.result = new UndecodedExchangeResult(httpRequest, response);
316318
}
317319

318320
@Override
319321
public StatusAssertions expectStatus() {
320-
return new StatusAssertions(this.exchangeResult, this);
322+
return new StatusAssertions(this.result, this);
321323
}
322324

323325
@Override
324326
public HeaderAssertions expectHeader() {
325-
return new HeaderAssertions(this.exchangeResult, this);
327+
return new HeaderAssertions(this.result, this);
326328
}
327329

328330
@Override
@@ -332,43 +334,44 @@ public TypeBodySpec expectBody(Class<?> elementType) {
332334

333335
@Override
334336
public TypeBodySpec expectBody(ResolvableType elementType) {
335-
return new DefaultTypeBodySpec(this.exchangeResult, elementType);
337+
return new DefaultTypeBodySpec(this.result, elementType);
336338
}
337339

338340
@Override
339341
public BodySpec expectBody() {
340-
return new DefaultBodySpec(this.exchangeResult);
342+
return new DefaultBodySpec(this.result);
341343
}
342344

343345
@Override
344346
public ResponseSpec consumeWith(Consumer<ExchangeResult> consumer) {
345-
consumer.accept(this.exchangeResult);
346-
return this;
347+
return this.result.assertWithDiagnosticsAndReturn(() -> {
348+
consumer.accept(this.result);
349+
return this;
350+
});
347351
}
348352

349353
@Override
350354
public ExchangeResult returnResult() {
351-
return this.exchangeResult;
355+
return this.result;
352356
}
353357
}
354358

355359
private class DefaultTypeBodySpec implements TypeBodySpec {
356360

357-
private final UndecodedExchangeResult exchangeResult;
361+
private final UndecodedExchangeResult result;
358362

359363
private final ResolvableType elementType;
360364

361365

362366
public DefaultTypeBodySpec(UndecodedExchangeResult result, ResolvableType elementType) {
363-
this.exchangeResult = result;
367+
this.result = result;
364368
this.elementType = elementType;
365369
}
366370

367371

368372
@Override
369373
public SingleValueBodySpec value() {
370-
EntityExchangeResult<?> completed = this.exchangeResult.consumeSingle(this.elementType);
371-
return new DefaultSingleValueBodySpec(completed);
374+
return new DefaultSingleValueBodySpec(this.result.consumeSingle(this.elementType));
372375
}
373376

374377
@Override
@@ -378,52 +381,55 @@ public ListBodySpec list() {
378381

379382
@Override
380383
public ListBodySpec list(int count) {
381-
EntityExchangeResult<List<?>> completed = this.exchangeResult.consumeList(this.elementType, count);
382-
return new DefaultListBodySpec(completed);
384+
return new DefaultListBodySpec(this.result.consumeList(this.elementType, count));
383385
}
384386

385387
@Override
386388
public <T> FluxExchangeResult<T> returnResult() {
387-
return this.exchangeResult.decodeBody(this.elementType);
389+
return this.result.decodeBody(this.elementType);
388390
}
389391
}
390392

391393
private class DefaultSingleValueBodySpec implements SingleValueBodySpec {
392394

393-
private final EntityExchangeResult<?> exchangeResult;
395+
private final EntityExchangeResult<?> result;
394396

395397

396398
public DefaultSingleValueBodySpec(EntityExchangeResult<?> result) {
397-
this.exchangeResult = result;
399+
this.result = result;
398400
}
399401

400402

401403
@Override
402404
public <T> EntityExchangeResult<T> isEqualTo(T expected) {
403-
assertEquals("Response body", expected, this.exchangeResult.getResponseBody());
404-
return returnResult();
405+
return this.result.assertWithDiagnosticsAndReturn(() -> {
406+
assertEquals("Response body", expected, this.result.getResponseBody());
407+
return returnResult();
408+
});
405409
}
406410

407411
@Override
408412
public <T> EntityExchangeResult<T> returnResult() {
409-
return new EntityExchangeResult<>(this.exchangeResult, (T) this.exchangeResult.getResponseBody());
413+
return new EntityExchangeResult<>(this.result, (T) this.result.getResponseBody());
410414
}
411415
}
412416

413417
private class DefaultListBodySpec implements ListBodySpec {
414418

415-
private final EntityExchangeResult<List<?>> exchangeResult;
419+
private final EntityExchangeResult<List<?>> result;
416420

417421

418422
public DefaultListBodySpec(EntityExchangeResult<List<?>> result) {
419-
this.exchangeResult = result;
423+
this.result = result;
420424
}
421425

422426

423427
@Override
424428
public <T> EntityExchangeResult<List<T>> isEqualTo(List<T> expected) {
425-
assertEquals("Response body", expected, this.exchangeResult.getResponseBody());
426-
return returnResult();
429+
return this.result.assertWithDiagnosticsAndReturn(() -> {
430+
assertEquals("Response body", expected, this.result.getResponseBody());
431+
return returnResult();
432+
});
427433
}
428434

429435
@Override
@@ -433,24 +439,28 @@ public ListBodySpec hasSize(int size) {
433439

434440
@Override
435441
public ListBodySpec contains(Object... elements) {
436-
List<Object> elementList = Arrays.asList(elements);
437-
String message = "Response body does not contain " + elementList;
438-
assertTrue(message, this.exchangeResult.getResponseBody().containsAll(elementList));
442+
this.result.assertWithDiagnostics(() -> {
443+
List<Object> elementList = Arrays.asList(elements);
444+
String message = "Response body does not contain " + elementList;
445+
assertTrue(message, this.result.getResponseBody().containsAll(elementList));
446+
});
439447
return this;
440448
}
441449

442450
@Override
443451
public ListBodySpec doesNotContain(Object... elements) {
444-
List<Object> elementList = Arrays.asList(elements);
445-
String message = "Response body should have contained " + elementList;
446-
assertTrue(message, !this.exchangeResult.getResponseBody().containsAll(Arrays.asList(elements)));
452+
this.result.assertWithDiagnostics(() -> {
453+
List<Object> elementList = Arrays.asList(elements);
454+
String message = "Response body should have contained " + elementList;
455+
assertTrue(message, !this.result.getResponseBody().containsAll(Arrays.asList(elements)));
456+
});
447457
return this;
448458
}
449459

450460
@Override
451461
@SuppressWarnings("unchecked")
452462
public <T> EntityExchangeResult<List<T>> returnResult() {
453-
return new EntityExchangeResult<>(this.exchangeResult, (List<T>) this.exchangeResult.getResponseBody());
463+
return new EntityExchangeResult<>(this.result, (List<T>) this.result.getResponseBody());
454464
}
455465
}
456466

@@ -476,61 +486,70 @@ public MapBodySpec map(Class<?> keyType, Class<?> valueType) {
476486

477487
@Override
478488
public MapBodySpec map(ResolvableType keyType, ResolvableType valueType) {
479-
EntityExchangeResult<Map<?, ?>> completed = this.exchangeResult.consumeMap(keyType, valueType);
480-
return new DefaultMapBodySpec(completed);
489+
return new DefaultMapBodySpec(this.exchangeResult.consumeMap(keyType, valueType));
481490
}
482491
}
483492

484493
private class DefaultMapBodySpec implements MapBodySpec {
485494

486-
private final EntityExchangeResult<Map<?, ?>> exchangeResult;
495+
private final EntityExchangeResult<Map<?, ?>> result;
487496

488497

489498
public DefaultMapBodySpec(EntityExchangeResult<Map<?, ?>> result) {
490-
this.exchangeResult = result;
499+
this.result = result;
491500
}
492501

493502

494503
private Map<?, ?> getBody() {
495-
return this.exchangeResult.getResponseBody();
504+
return this.result.getResponseBody();
496505
}
497506

498507
@Override
499508
public <K, V> EntityExchangeResult<Map<K, V>> isEqualTo(Map<K, V> expected) {
500-
assertEquals("Response body map", expected, getBody());
501-
return returnResult();
509+
return this.result.assertWithDiagnosticsAndReturn(() -> {
510+
assertEquals("Response body map", expected, getBody());
511+
return returnResult();
512+
});
502513
}
503514

504515
@Override
505516
public MapBodySpec hasSize(int size) {
506-
assertEquals("Response body map size", size, getBody().size());
507-
return this;
517+
return this.result.assertWithDiagnosticsAndReturn(() -> {
518+
assertEquals("Response body map size", size, getBody().size());
519+
return this;
520+
});
508521
}
509522

510523
@Override
511524
public MapBodySpec contains(Object key, Object value) {
512-
assertEquals("Response body map value for key " + key, value, getBody().get(key));
513-
return this;
525+
return this.result.assertWithDiagnosticsAndReturn(() -> {
526+
assertEquals("Response body map value for key " + key, value, getBody().get(key));
527+
return this;
528+
});
514529
}
515530

516531
@Override
517532
public MapBodySpec containsKeys(Object... keys) {
518-
List<?> missing = Arrays.stream(keys).filter(k -> !getBody().containsKey(k)).collect(toList());
519-
assertTrue("Response body map does not contain keys " + missing, missing.isEmpty());
520-
return this;
533+
return this.result.assertWithDiagnosticsAndReturn(() -> {
534+
List<?> missing = Arrays.stream(keys).filter(k -> !getBody().containsKey(k)).collect(toList());
535+
assertTrue("Response body map does not contain keys " + missing, missing.isEmpty());
536+
return this;
537+
});
521538
}
522539

523540
@Override
524541
public MapBodySpec containsValues(Object... values) {
525-
List<?> missing = Arrays.stream(values).filter(v -> !getBody().containsValue(v)).collect(toList());
526-
assertTrue("Response body map does not contain values " + missing, missing.isEmpty());
542+
this.result.assertWithDiagnostics(() -> {
543+
List<?> missing = Arrays.stream(values).filter(v -> !getBody().containsValue(v)).collect(toList());
544+
assertTrue("Response body map does not contain values " + missing, missing.isEmpty());
545+
});
527546
return this;
528547
}
529548

530549
@Override
531550
@SuppressWarnings("unchecked")
532551
public <K, V> EntityExchangeResult<Map<K, V>> returnResult() {
533-
return new EntityExchangeResult<>(this.exchangeResult, (Map<K, V>) getBody());
552+
return new EntityExchangeResult<>(this.result, (Map<K, V>) getBody());
534553
}
535554
}
536555

spring-test/src/main/java/org/springframework/test/web/reactive/server/EntityExchangeResult.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ public class EntityExchangeResult<T> extends ExchangeResult {
2929
private final T body;
3030

3131

32-
EntityExchangeResult(ExchangeResult exchange, T body) {
33-
super(exchange);
32+
EntityExchangeResult(ExchangeResult result, T body) {
33+
super(result);
3434
this.body = body;
3535
}
3636

spring-test/src/main/java/org/springframework/test/web/reactive/server/ExchangeResult.java

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package org.springframework.test.web.reactive.server;
1717

1818
import java.net.URI;
19+
import java.util.function.Supplier;
1920
import java.util.stream.Collectors;
2021

2122
import org.springframework.http.HttpHeaders;
@@ -59,12 +60,12 @@ public class ExchangeResult {
5960
this.responseHeaders = response.headers().asHttpHeaders();
6061
}
6162

62-
ExchangeResult(ExchangeResult exchange) {
63-
this.method = exchange.getMethod();
64-
this.url = exchange.getUrl();
65-
this.requestHeaders = exchange.getRequestHeaders();
66-
this.status = exchange.getStatus();
67-
this.responseHeaders = exchange.getResponseHeaders();
63+
ExchangeResult(ExchangeResult result) {
64+
this.method = result.getMethod();
65+
this.url = result.getUrl();
66+
this.requestHeaders = result.getRequestHeaders();
67+
this.status = result.getStatus();
68+
this.responseHeaders = result.getResponseHeaders();
6869
}
6970

7071

@@ -104,18 +105,52 @@ public HttpHeaders getResponseHeaders() {
104105
}
105106

106107

108+
/**
109+
* Execute the given Runnable in the context of "this" instance and decorate
110+
* any {@link AssertionError}s raised with request and response details.
111+
*/
112+
public void assertWithDiagnostics(Runnable assertion) {
113+
try {
114+
assertion.run();
115+
}
116+
catch (AssertionError ex) {
117+
throw new AssertionError("Assertion failed on the following exchange:" + this, ex);
118+
}
119+
}
120+
121+
/**
122+
* Variant of {@link #assertWithDiagnostics(Runnable)} that passes through
123+
* a return value from the assertion code.
124+
*/
125+
public <T> T assertWithDiagnosticsAndReturn(Supplier<T> assertion) {
126+
try {
127+
return assertion.get();
128+
}
129+
catch (AssertionError ex) {
130+
throw new AssertionError("Assertion failed on the following exchange:" + this, ex);
131+
}
132+
}
133+
134+
107135
@Override
108136
public String toString() {
109-
HttpStatus status = this.status;
110137
return "\n\n" +
111138
formatValue("Request", this.method + " " + getUrl()) +
112-
formatValue("Status", status + " " + status.getReasonPhrase()) +
139+
formatValue("Status", this.status + " " + getStatusReason()) +
113140
formatHeading("Response Headers") + formatHeaders(this.responseHeaders) +
114141
formatHeading("Request Headers") + formatHeaders(this.requestHeaders) +
115142
"\n" +
116143
formatValue("Response Body", formatResponseBody());
117144
}
118145

146+
private String getStatusReason() {
147+
String reason = "";
148+
if (this.status != null && this.status.getReasonPhrase() != null) {
149+
reason = this.status.getReasonPhrase();
150+
}
151+
return reason;
152+
}
153+
119154
private String formatHeading(String heading) {
120155
return "\n" + String.format("%s", heading) + "\n";
121156
}

spring-test/src/main/java/org/springframework/test/web/reactive/server/FluxExchangeResult.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ public class FluxExchangeResult<T> extends ExchangeResult {
3434
private final ResolvableType elementType;
3535

3636

37-
FluxExchangeResult(ExchangeResult exchange, Flux<T> body, ResolvableType elementType) {
38-
super(exchange);
37+
FluxExchangeResult(ExchangeResult result, Flux<T> body, ResolvableType elementType) {
38+
super(result);
3939
this.body = body;
4040
this.elementType = elementType;
4141
}

0 commit comments

Comments
 (0)