From e1b2f4be5cf6bb6bc31f93ea423ee4f3ee54a118 Mon Sep 17 00:00:00 2001 From: Diego Berrueta Date: Tue, 23 Apr 2019 07:55:16 +1000 Subject: [PATCH] Configure coordinated JSON provider in json-path. Fixes gh-15727 --- .../test/json/AbstractJsonMarshalTester.java | 37 +++++++- .../boot/test/json/BasicJsonTester.java | 36 +++++++- .../boot/test/json/GsonTester.java | 16 +++- .../boot/test/json/JacksonTester.java | 7 +- .../boot/test/json/JsonContent.java | 20 ++++- .../boot/test/json/JsonContentAssert.java | 20 ++++- .../boot/test/json/JsonbTester.java | 17 +++- .../test/json/GsonTesterIntegrationTests.java | 87 +++++++++++++++++++ .../json/JacksonTesterIntegrationTests.java | 24 +++++ .../boot/test/json/JsonContentTests.java | 29 +++++-- 10 files changed, 276 insertions(+), 17 deletions(-) create mode 100644 spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/json/GsonTesterIntegrationTests.java diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/json/AbstractJsonMarshalTester.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/json/AbstractJsonMarshalTester.java index 29f4915ffc98..f19b61645093 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/json/AbstractJsonMarshalTester.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/json/AbstractJsonMarshalTester.java @@ -26,6 +26,7 @@ import java.io.StringReader; import java.lang.reflect.Field; +import com.jayway.jsonpath.Configuration; import org.assertj.core.api.Assertions; import org.springframework.beans.factory.ObjectFactory; @@ -72,6 +73,8 @@ public abstract class AbstractJsonMarshalTester { private ResolvableType type; + private Configuration configuration; + /** * Create a new uninitialized {@link AbstractJsonMarshalTester} instance. */ @@ -85,9 +88,22 @@ protected AbstractJsonMarshalTester() { * @param type the type under test */ public AbstractJsonMarshalTester(Class resourceLoadClass, ResolvableType type) { + this(resourceLoadClass, type, Configuration.defaultConfiguration()); + } + + /** + * Create a new {@link AbstractJsonMarshalTester} instance. + * @param resourceLoadClass the source class used when loading relative classpath + * resources + * @param type the type under test + * @param configuration the json-path configuration + */ + public AbstractJsonMarshalTester(Class resourceLoadClass, ResolvableType type, + Configuration configuration) { Assert.notNull(resourceLoadClass, "ResourceLoadClass must not be null"); Assert.notNull(type, "Type must not be null"); - initialize(resourceLoadClass, type); + Assert.notNull(configuration, "Configuration must not be null"); + initialize(resourceLoadClass, type, configuration); } /** @@ -97,9 +113,23 @@ public AbstractJsonMarshalTester(Class resourceLoadClass, ResolvableType type * @param type the type under test */ protected final void initialize(Class resourceLoadClass, ResolvableType type) { - if (this.resourceLoadClass == null && this.type == null) { + initialize(resourceLoadClass, type, Configuration.defaultConfiguration()); + } + + /** + * Initialize the marshal tester for use. + * @param resourceLoadClass the source class used when loading relative classpath + * resources + * @param type the type under test + * @param configuration the json-path configuration + */ + protected final void initialize(Class resourceLoadClass, ResolvableType type, + Configuration configuration) { + if (this.resourceLoadClass == null && this.type == null + && this.configuration == null) { this.resourceLoadClass = resourceLoadClass; this.type = type; + this.configuration = configuration; } } @@ -129,7 +159,8 @@ public JsonContent write(T value) throws IOException { verify(); Assert.notNull(value, "Value must not be null"); String json = writeObject(value, this.type); - return new JsonContent<>(this.resourceLoadClass, this.type, json); + return new JsonContent<>(this.resourceLoadClass, this.type, json, + this.configuration); } /** diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/json/BasicJsonTester.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/json/BasicJsonTester.java index e602e401962a..eaacf08f017f 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/json/BasicJsonTester.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/json/BasicJsonTester.java @@ -20,6 +20,8 @@ import java.io.InputStream; import java.nio.charset.Charset; +import com.jayway.jsonpath.Configuration; + import org.springframework.core.io.Resource; import org.springframework.util.Assert; @@ -49,6 +51,8 @@ public class BasicJsonTester { private JsonLoader loader; + private Configuration configuration; + /** * Create a new uninitialized {@link BasicJsonTester} instance. */ @@ -70,8 +74,21 @@ public BasicJsonTester(Class resourceLoadClass) { * @since 1.4.1 */ public BasicJsonTester(Class resourceLoadClass, Charset charset) { + this(resourceLoadClass, charset, Configuration.defaultConfiguration()); + } + + /** + * Create a new {@link BasicJsonTester} instance. + * @param resourceLoadClass the source class used to load resources + * @param charset the charset used to load resources + * @param configuration the json-path configuration + */ + public BasicJsonTester(Class resourceLoadClass, Charset charset, + Configuration configuration) { Assert.notNull(resourceLoadClass, "ResourceLoadClass must not be null"); + Assert.notNull(configuration, "Configuration must not be null"); this.loader = new JsonLoader(resourceLoadClass, charset); + this.configuration = configuration; } /** @@ -92,8 +109,22 @@ protected final void initialize(Class resourceLoadClass) { * @since 1.4.1 */ protected final void initialize(Class resourceLoadClass, Charset charset) { - if (this.loader == null) { + initialize(resourceLoadClass, charset, Configuration.defaultConfiguration()); + } + + /** + * Initialize the marshal tester for use. + * @param resourceLoadClass the source class used when loading relative classpath + * resources + * @param charset the charset used when loading relative classpath resources + * @param configuration the json-path configuration + * @since + */ + protected final void initialize(Class resourceLoadClass, Charset charset, + Configuration configuration) { + if (this.loader == null && this.configuration == null) { this.loader = new JsonLoader(resourceLoadClass, charset); + this.configuration = configuration; } } @@ -165,7 +196,8 @@ private void verify() { } private JsonContent getJsonContent(String json) { - return new JsonContent<>(this.loader.getResourceLoadClass(), null, json); + return new JsonContent<>(this.loader.getResourceLoadClass(), null, json, + this.configuration); } } diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/json/GsonTester.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/json/GsonTester.java index 0f82f2fa1ac3..89f77d9a9448 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/json/GsonTester.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/json/GsonTester.java @@ -20,6 +20,7 @@ import java.io.Reader; import com.google.gson.Gson; +import com.jayway.jsonpath.Configuration; import org.springframework.beans.factory.ObjectFactory; import org.springframework.core.ResolvableType; @@ -74,7 +75,20 @@ protected GsonTester(Gson gson) { * @see #initFields(Object, Gson) */ public GsonTester(Class resourceLoadClass, ResolvableType type, Gson gson) { - super(resourceLoadClass, type); + this(resourceLoadClass, type, gson, Configuration.defaultConfiguration()); + } + + /** + * Create a new {@link GsonTester} instance. + * @param resourceLoadClass the source class used to load resources + * @param type the type under test + * @param gson the Gson instance + * @param configuration the json-path configuration + * @see #initFields(Object, Gson) + */ + public GsonTester(Class resourceLoadClass, ResolvableType type, Gson gson, + Configuration configuration) { + super(resourceLoadClass, type, configuration); Assert.notNull(gson, "Gson must not be null"); this.gson = gson; } diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/json/JacksonTester.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/json/JacksonTester.java index afe00a22f16a..a5d66a14a5fe 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/json/JacksonTester.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/json/JacksonTester.java @@ -24,6 +24,9 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectReader; import com.fasterxml.jackson.databind.ObjectWriter; +import com.jayway.jsonpath.Configuration; +import com.jayway.jsonpath.spi.json.JacksonJsonProvider; +import com.jayway.jsonpath.spi.mapper.JacksonMappingProvider; import org.springframework.beans.factory.ObjectFactory; import org.springframework.core.ResolvableType; @@ -86,7 +89,9 @@ public JacksonTester(Class resourceLoadClass, ResolvableType type, public JacksonTester(Class resourceLoadClass, ResolvableType type, ObjectMapper objectMapper, Class view) { - super(resourceLoadClass, type); + super(resourceLoadClass, type, Configuration.builder() + .jsonProvider(new JacksonJsonProvider(objectMapper)) + .mappingProvider(new JacksonMappingProvider(objectMapper)).build()); Assert.notNull(objectMapper, "ObjectMapper must not be null"); this.objectMapper = objectMapper; this.view = view; diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/json/JsonContent.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/json/JsonContent.java index 87eb7be294a3..8c4d61f7303b 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/json/JsonContent.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/json/JsonContent.java @@ -16,6 +16,7 @@ package org.springframework.boot.test.json; +import com.jayway.jsonpath.Configuration; import org.assertj.core.api.AssertProvider; import org.springframework.core.ResolvableType; @@ -38,6 +39,8 @@ public final class JsonContent implements AssertProvider { private final String json; + private final Configuration configuration; + /** * Create a new {@link JsonContent} instance. * @param resourceLoadClass the source class used to load resources @@ -45,11 +48,25 @@ public final class JsonContent implements AssertProvider { * @param json the actual JSON content */ public JsonContent(Class resourceLoadClass, ResolvableType type, String json) { + this(resourceLoadClass, type, json, Configuration.defaultConfiguration()); + } + + /** + * Create a new {@link JsonContent} instance. + * @param resourceLoadClass the source class used to load resources + * @param type the type under test (or {@code null} if not known) + * @param json the actual JSON content + * @param configuration the json-path configuration + */ + public JsonContent(Class resourceLoadClass, ResolvableType type, String json, + Configuration configuration) { Assert.notNull(resourceLoadClass, "ResourceLoadClass must not be null"); Assert.notNull(json, "JSON must not be null"); + Assert.notNull(configuration, "Configuration must not be null"); this.resourceLoadClass = resourceLoadClass; this.type = type; this.json = json; + this.configuration = configuration; } /** @@ -61,7 +78,8 @@ public JsonContent(Class resourceLoadClass, ResolvableType type, String json) @Override @Deprecated public JsonContentAssert assertThat() { - return new JsonContentAssert(this.resourceLoadClass, this.json); + return new JsonContentAssert(this.resourceLoadClass, null, this.json, + this.configuration); } /** diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/json/JsonContentAssert.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/json/JsonContentAssert.java index 733274c19399..00eafc7af35a 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/json/JsonContentAssert.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/json/JsonContentAssert.java @@ -22,6 +22,7 @@ import java.util.List; import java.util.Map; +import com.jayway.jsonpath.Configuration; import com.jayway.jsonpath.JsonPath; import org.assertj.core.api.AbstractAssert; import org.assertj.core.api.AbstractBooleanAssert; @@ -52,6 +53,8 @@ public class JsonContentAssert extends AbstractAssert resourceLoadClass, CharSequence json) { */ public JsonContentAssert(Class resourceLoadClass, Charset charset, CharSequence json) { + this(resourceLoadClass, charset, json, Configuration.defaultConfiguration()); + } + + /** + * Create a new {@link JsonContentAssert} instance that will load resources in the + * given {@code charset}. + * @param resourceLoadClass the source class used to load resources + * @param charset the charset of the JSON resources + * @param json the actual JSON content + * @param configuration the json-path configuration + */ + public JsonContentAssert(Class resourceLoadClass, Charset charset, + CharSequence json, Configuration configuration) { super(json, JsonContentAssert.class); + this.configuration = configuration; this.loader = new JsonLoader(resourceLoadClass, charset); } @@ -1110,7 +1127,8 @@ private boolean isEmpty() { public Object getValue(boolean required) { try { CharSequence json = JsonContentAssert.this.actual; - return this.jsonPath.read((json != null) ? json.toString() : null); + return this.jsonPath.read((json != null) ? json.toString() : null, + JsonContentAssert.this.configuration); } catch (Exception ex) { if (required) { diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/json/JsonbTester.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/json/JsonbTester.java index 8b3b754e73c9..63acafab74f8 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/json/JsonbTester.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/json/JsonbTester.java @@ -21,6 +21,8 @@ import javax.json.bind.Jsonb; +import com.jayway.jsonpath.Configuration; + import org.springframework.beans.factory.ObjectFactory; import org.springframework.core.ResolvableType; import org.springframework.util.Assert; @@ -74,7 +76,20 @@ protected JsonbTester(Jsonb jsonb) { * @see #initFields(Object, Jsonb) */ public JsonbTester(Class resourceLoadClass, ResolvableType type, Jsonb jsonb) { - super(resourceLoadClass, type); + this(resourceLoadClass, type, jsonb, Configuration.defaultConfiguration()); + } + + /** + * Create a new {@link JsonbTester} instance. + * @param resourceLoadClass the source class used to load resources + * @param type the type under test + * @param jsonb the Jsonb instance + * @param configuration the json-path configuration + * @see #initFields(Object, Jsonb) + */ + public JsonbTester(Class resourceLoadClass, ResolvableType type, Jsonb jsonb, + Configuration configuration) { + super(resourceLoadClass, type, configuration); Assert.notNull(jsonb, "Jsonb must not be null"); this.jsonb = jsonb; } diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/json/GsonTesterIntegrationTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/json/GsonTesterIntegrationTests.java new file mode 100644 index 000000000000..4bc298ac016c --- /dev/null +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/json/GsonTesterIntegrationTests.java @@ -0,0 +1,87 @@ +/* + * Copyright 2012-2019 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 + * + * https://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.boot.test.json; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import com.google.gson.Gson; +import org.junit.Before; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link GsonTester}. Shows typical usage. + * + * @author Andy Wilkinson + * @author Diego Berrueta + */ +public class GsonTesterIntegrationTests { + + private GsonTester simpleJson; + + private GsonTester> listJson; + + private GsonTester> mapJson; + + private GsonTester stringJson; + + private Gson gson; + + private static final String JSON = "{\"name\":\"Spring\",\"age\":123}"; + + @Before + public void setup() { + this.gson = new Gson(); + GsonTester.initFields(this, this.gson); + } + + @Test + public void typicalTest() throws Exception { + String example = JSON; + assertThat(this.simpleJson.parse(example).getObject().getName()) + .isEqualTo("Spring"); + } + + @Test + public void typicalListTest() throws Exception { + String example = "[" + JSON + "]"; + assertThat(this.listJson.parse(example)).asList().hasSize(1); + assertThat(this.listJson.parse(example).getObject().get(0).getName()) + .isEqualTo("Spring"); + } + + @Test + public void typicalMapTest() throws Exception { + Map map = new LinkedHashMap<>(); + map.put("a", 1); + map.put("b", 2); + assertThat(this.mapJson.write(map)).extractingJsonPathNumberValue("@.a") + .isEqualTo(1); + } + + @Test + public void stringLiteral() throws Exception { + String stringWithSpecialCharacters = "myString"; + assertThat(this.stringJson.write(stringWithSpecialCharacters)) + .extractingJsonPathStringValue("@") + .isEqualTo(stringWithSpecialCharacters); + } + +} diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/json/JacksonTesterIntegrationTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/json/JacksonTesterIntegrationTests.java index c51030637096..46fe2f848cd3 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/json/JacksonTesterIntegrationTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/json/JacksonTesterIntegrationTests.java @@ -47,6 +47,8 @@ public class JacksonTesterIntegrationTests { private JacksonTester> mapJson; + private JacksonTester stringJson; + private ObjectMapper objectMapper; private static final String JSON = "{\"name\":\"Spring\",\"age\":123}"; @@ -81,6 +83,28 @@ public void typicalMapTest() throws Exception { .isEqualTo(1); } + @Test + public void stringLiteral() throws Exception { + String stringWithSpecialCharacters = "myString"; + assertThat(this.stringJson.write(stringWithSpecialCharacters)) + .extractingJsonPathStringValue("@") + .isEqualTo(stringWithSpecialCharacters); + } + + // This test confirms that the handling of special characters is symmetrical between + // the serialisation (via the JacksonTester) and the parsing (via json-path). By + // default json-path uses SimpleJson as its parser, which has a slightly different + // behaviour to Jackson and breaks the symmetry. However JacksonTester + // configures json-path to use Jackson for evaluating the path expressions and + // restores the symmetry. + @Test + public void parseSpecialCharactersTest() throws Exception { + String stringWithSpecialCharacters = "\u0006\u007F"; + assertThat(this.stringJson.write(stringWithSpecialCharacters)) + .extractingJsonPathStringValue("@") + .isEqualTo(stringWithSpecialCharacters); + } + @Test public void writeWithView() throws Exception { this.objectMapper.disable(MapperFeature.DEFAULT_VIEW_INCLUSION); diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/json/JsonContentTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/json/JsonContentTests.java index bf0859f21612..a27f83d8b891 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/json/JsonContentTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/json/JsonContentTests.java @@ -16,6 +16,7 @@ package org.springframework.boot.test.json; +import com.jayway.jsonpath.Configuration; import org.junit.Test; import org.springframework.core.ResolvableType; @@ -38,47 +39,61 @@ public class JsonContentTests { @Test public void createWhenResourceLoadClassIsNullShouldThrowException() { assertThatIllegalArgumentException() - .isThrownBy(() -> new JsonContent(null, TYPE, JSON)) + .isThrownBy(() -> new JsonContent(null, TYPE, JSON, + Configuration.defaultConfiguration())) .withMessageContaining("ResourceLoadClass must not be null"); } @Test public void createWhenJsonIsNullShouldThrowException() { assertThatIllegalArgumentException() - .isThrownBy(() -> new JsonContent(getClass(), TYPE, null)) + .isThrownBy(() -> new JsonContent(getClass(), TYPE, null, + Configuration.defaultConfiguration())) .withMessageContaining("JSON must not be null"); } + @Test + public void createWhenConfigurationIsNullShouldThrowException() { + assertThatIllegalArgumentException().isThrownBy( + () -> new JsonContent(getClass(), TYPE, JSON, null)) + .withMessageContaining("Configuration must not be null"); + } + @Test public void createWhenTypeIsNullShouldCreateContent() { - JsonContent content = new JsonContent<>(getClass(), null, JSON); + JsonContent content = new JsonContent<>(getClass(), null, JSON, + Configuration.defaultConfiguration()); assertThat(content).isNotNull(); } @Test @SuppressWarnings("deprecation") public void assertThatShouldReturnJsonContentAssert() { - JsonContent content = new JsonContent<>(getClass(), TYPE, JSON); + JsonContent content = new JsonContent<>(getClass(), TYPE, JSON, + Configuration.defaultConfiguration()); assertThat(content.assertThat()).isInstanceOf(JsonContentAssert.class); } @Test public void getJsonShouldReturnJson() { - JsonContent content = new JsonContent<>(getClass(), TYPE, JSON); + JsonContent content = new JsonContent<>(getClass(), TYPE, JSON, + Configuration.defaultConfiguration()); assertThat(content.getJson()).isEqualTo(JSON); } @Test public void toStringWhenHasTypeShouldReturnString() { - JsonContent content = new JsonContent<>(getClass(), TYPE, JSON); + JsonContent content = new JsonContent<>(getClass(), TYPE, JSON, + Configuration.defaultConfiguration()); assertThat(content.toString()) .isEqualTo("JsonContent " + JSON + " created from " + TYPE); } @Test public void toStringWhenHasNoTypeShouldReturnString() { - JsonContent content = new JsonContent<>(getClass(), null, JSON); + JsonContent content = new JsonContent<>(getClass(), null, JSON, + Configuration.defaultConfiguration()); assertThat(content.toString()).isEqualTo("JsonContent " + JSON); }