From 7ea2caa82cd52241d5e33640e5813a1a90620e01 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Fri, 14 Apr 2017 16:36:07 -0400 Subject: [PATCH] JSON content and JsonPath support for WebTestClient Issue: SPR-15420 --- .../reactive/server/DefaultWebTestClient.java | 19 +++ .../reactive/server/JsonPathAssertions.java | 115 ++++++++++++++++++ .../web/reactive/server/WebTestClient.java | 24 +++- .../server/samples/JsonContentTests.java | 69 +++++++++++ .../web/reactive/server/samples/Person.java | 52 ++++++++ .../server/samples/ResponseEntityTests.java | 34 ------ 6 files changed, 277 insertions(+), 36 deletions(-) create mode 100644 spring-test/src/main/java/org/springframework/test/web/reactive/server/JsonPathAssertions.java create mode 100644 spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/JsonContentTests.java create mode 100644 spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/Person.java diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClient.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClient.java index 69db340ce2..894e1eb398 100644 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClient.java +++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClient.java @@ -40,6 +40,7 @@ import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.http.client.reactive.ClientHttpConnector; import org.springframework.http.client.reactive.ClientHttpRequest; +import org.springframework.test.util.JsonExpectationsHelper; import org.springframework.util.Assert; import org.springframework.util.MimeType; import org.springframework.util.MultiValueMap; @@ -502,6 +503,24 @@ class DefaultWebTestClient implements WebTestClient { return new EntityExchangeResult<>(this.result, null); } + @Override + public BodyContentSpec json(String json) { + this.result.assertWithDiagnostics(() -> { + try { + new JsonExpectationsHelper().assertJsonEqual(json, getBodyAsString()); + } + catch (Exception ex) { + throw new AssertionError("JSON parsing error", ex); + } + }); + return this; + } + + @Override + public JsonPathAssertions jsonPath(String expression, Object... args) { + return new JsonPathAssertions(this, expression, args); + } + @Override public BodyContentSpec consumeAsStringWith(Consumer consumer) { this.result.assertWithDiagnostics(() -> consumer.accept(getBodyAsString())); diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/JsonPathAssertions.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/JsonPathAssertions.java new file mode 100644 index 0000000000..30d9b69de8 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/JsonPathAssertions.java @@ -0,0 +1,115 @@ +/* + * Copyright 2002-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.test.web.reactive.server; + +import org.springframework.test.util.JsonPathExpectationsHelper; + + +/** + * JsonPath assertions. + * + * @author Rossen Stoyanchev + * @since 5.0 + * @see https://github.com/jayway/JsonPath + */ +public class JsonPathAssertions { + + private final WebTestClient.BodyContentSpec bodySpec; + + private final JsonPathExpectationsHelper pathHelper; + + + JsonPathAssertions(WebTestClient.BodyContentSpec spec, String expression, Object... args) { + this.bodySpec = spec; + this.pathHelper = new JsonPathExpectationsHelper(expression, args); + } + + + /** + * Applies {@link JsonPathExpectationsHelper#assertValue(String, Object)}. + */ + public WebTestClient.BodyContentSpec isEqualTo(Object expectedValue) { + this.bodySpec.consumeAsStringWith(body -> { + this.pathHelper.assertValue(body, expectedValue); + }); + return this.bodySpec; + } + + /** + * Applies {@link JsonPathExpectationsHelper#exists(String)}. + */ + public WebTestClient.BodyContentSpec exists() { + this.bodySpec.consumeAsStringWith(this.pathHelper::exists); + return this.bodySpec; + } + + /** + * Applies {@link JsonPathExpectationsHelper#doesNotExist(String)}. + */ + public WebTestClient.BodyContentSpec doesNotExist() { + this.bodySpec.consumeAsStringWith(this.pathHelper::doesNotExist); + return this.bodySpec; + } + + /** + * Applies {@link JsonPathExpectationsHelper#assertValueIsEmpty(String)}. + */ + public WebTestClient.BodyContentSpec isEmpty() { + this.bodySpec.consumeAsStringWith(this.pathHelper::assertValueIsEmpty); + return this.bodySpec; + } + + /** + * Applies {@link JsonPathExpectationsHelper#assertValueIsNotEmpty(String)}. + */ + public WebTestClient.BodyContentSpec isNotEmpty() { + this.bodySpec.consumeAsStringWith(this.pathHelper::assertValueIsNotEmpty); + return this.bodySpec; + } + + /** + * Applies {@link JsonPathExpectationsHelper#assertValueIsBoolean(String)}. + */ + public WebTestClient.BodyContentSpec isBoolean() { + this.bodySpec.consumeAsStringWith(this.pathHelper::assertValueIsBoolean); + return this.bodySpec; + } + + /** + * Applies {@link JsonPathExpectationsHelper#assertValueIsNumber(String)}. + */ + public WebTestClient.BodyContentSpec isNumber() { + this.bodySpec.consumeAsStringWith(this.pathHelper::assertValueIsNumber); + return this.bodySpec; + } + + /** + * Applies {@link JsonPathExpectationsHelper#assertValueIsArray(String)}. + */ + public WebTestClient.BodyContentSpec isArray() { + this.bodySpec.consumeAsStringWith(this.pathHelper::assertValueIsArray); + return this.bodySpec; + } + + /** + * Applies {@link JsonPathExpectationsHelper#assertValueIsMap(String)}. + */ + public WebTestClient.BodyContentSpec isMap() { + this.bodySpec.consumeAsStringWith(this.pathHelper::assertValueIsMap); + return this.bodySpec; + } + +} diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java index c4765b4a1c..3b0de40985 100644 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java +++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java @@ -598,11 +598,31 @@ public interface WebTestClient { interface BodyContentSpec { /** - * Consume the body and verify it is empty. - * @return the exchange result + * Assert the response body is empty and return the exchange result. */ EntityExchangeResult isEmpty(); + /** + * Parse the expected and actual response content as JSON and perform a + * "lenient" comparison verifying the same attribute-value pairs. + *

Use of this option requires the + * JSONassert library + * on to be on the classpath. + * @param expectedJson the expected JSON content. + */ + BodyContentSpec json(String expectedJson); + + /** + * Access to response body assertions using a + * JsonPath expression + * to inspect a specific subset of the body. + *

The JSON path expression can be a parameterized string using + * formatting specifiers as defined in {@link String#format}. + * @param expression the JsonPath expression + * @param args arguments to parameterize the expression + */ + JsonPathAssertions jsonPath(String expression, Object... args); + /** * Assert the response body content converted to a String with the given * {@link Consumer}. The String is created with the {@link Charset} from diff --git a/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/JsonContentTests.java b/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/JsonContentTests.java new file mode 100644 index 0000000000..f1fc414eaf --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/JsonContentTests.java @@ -0,0 +1,69 @@ +/* + * Copyright 2002-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.test.web.reactive.server.samples; + +import org.junit.Test; +import reactor.core.publisher.Flux; + +import org.springframework.http.MediaType; +import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RestController; + +/** + * Sample tests asserting JSON response content. + * + * @author Rossen Stoyanchev + */ +public class JsonContentTests { + + private final WebTestClient client = WebTestClient.bindToController(new PersonController()).build(); + + + @Test + public void jsonContent() throws Exception { + this.client.get().uri("/persons") + .accept(MediaType.APPLICATION_JSON_UTF8) + .exchange() + .expectStatus().isOk() + .expectBody().json("[{\"name\":\"Jane\"},{\"name\":\"Jason\"},{\"name\":\"John\"}]"); + } + + @Test + public void jsonPathIsEqualTo() throws Exception { + this.client.get().uri("/persons") + .accept(MediaType.APPLICATION_JSON_UTF8) + .exchange() + .expectStatus().isOk() + .expectBody() + .jsonPath("$[0].name").isEqualTo("Jane") + .jsonPath("$[1].name").isEqualTo("Jason") + .jsonPath("$[2].name").isEqualTo("John"); + } + + + @RestController + @SuppressWarnings("unused") + static class PersonController { + + @GetMapping("/persons") + Flux getPersons() { + return Flux.just(new Person("Jane"), new Person("Jason"), new Person("John")); + } + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/Person.java b/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/Person.java new file mode 100644 index 0000000000..ffe5bfa14a --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/Person.java @@ -0,0 +1,52 @@ +/* + * Copyright 2002-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.test.web.reactive.server.samples; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +class Person { + + private final String name; + + @JsonCreator + public Person(@JsonProperty("name") String name) { + this.name = name; + } + + public String getName() { + return this.name; + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + if (other == null || getClass() != other.getClass()) return false; + Person person = (Person) other; + return getName().equals(person.getName()); + } + + @Override + public int hashCode() { + return getName().hashCode(); + } + + @Override + public String toString() { + return "Person[name='" + name + "']"; + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/ResponseEntityTests.java b/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/ResponseEntityTests.java index 1144c9bd32..19f7dc2472 100644 --- a/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/ResponseEntityTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/ResponseEntityTests.java @@ -22,8 +22,6 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; import org.junit.Test; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -169,36 +167,4 @@ public class ResponseEntityTests { } } - static class Person { - - private final String name; - - @JsonCreator - public Person(@JsonProperty("name") String name) { - this.name = name; - } - - public String getName() { - return this.name; - } - - @Override - public boolean equals(Object other) { - if (this == other) return true; - if (other == null || getClass() != other.getClass()) return false; - Person person = (Person) other; - return getName().equals(person.getName()); - } - - @Override - public int hashCode() { - return getName().hashCode(); - } - - @Override - public String toString() { - return "Person[name='" + name + "']"; - } - } - }