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 29f4915ffc..f19b616450 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.Reader; 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 @@ public abstract class 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 abstract class AbstractJsonMarshalTester { * @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 abstract class AbstractJsonMarshalTester { 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 e602e40196..eaacf08f01 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.File; 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 class BasicJsonTester { * @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 @@ public class BasicJsonTester { * @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 @@ public class BasicJsonTester { } 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 0f82f2fa1a..89f77d9a94 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.IOException; 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 @@ public class GsonTester extends AbstractJsonMarshalTester { * @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 afe00a22f1..a5d66a14a5 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.JavaType; 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 class JacksonTester extends AbstractJsonMarshalTester { 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 87eb7be294..8c4d61f730 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 final class JsonContent implements AssertProvider { @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 87df230ed7..ef99439531 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.nio.charset.Charset; 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; @@ -51,6 +52,8 @@ public class JsonContentAssert extends AbstractAssert 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); } @@ -1109,7 +1126,8 @@ public class JsonContentAssert extends AbstractAssert extends AbstractJsonMarshalTester { * @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 0000000000..4bc298ac01 --- /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 8342bb2aa0..94ef07a80c 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 class JacksonTesterIntegrationTests { .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 bf0859f216..a27f83d8b8 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); }