Introduce JSON comparison abstraction for tests
This commit introduces a JsonComparator abstraction, with an implementation using JSONAssert. Previously, JSONAssert was the only choice. Test APIs have been adapted to allow the new abstraction while relying on JSONAssert still for high-level methods. Closes gh-32791 Co-authored-by: Stéphane Nicoll <stephane.nicoll@broadcom.com>
This commit is contained in:
committed by
Stéphane Nicoll
parent
80faa94afc
commit
d8dcd3af0b
@@ -28,10 +28,6 @@ import org.assertj.core.api.AbstractStringAssert;
|
||||
import org.assertj.core.api.AssertProvider;
|
||||
import org.assertj.core.error.BasicErrorMessageFactory;
|
||||
import org.assertj.core.internal.Failures;
|
||||
import org.skyscreamer.jsonassert.JSONCompare;
|
||||
import org.skyscreamer.jsonassert.JSONCompareMode;
|
||||
import org.skyscreamer.jsonassert.JSONCompareResult;
|
||||
import org.skyscreamer.jsonassert.comparator.JSONComparator;
|
||||
|
||||
import org.springframework.core.io.ByteArrayResource;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
@@ -41,7 +37,6 @@ import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.converter.GenericHttpMessageConverter;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.function.ThrowingBiFunction;
|
||||
|
||||
/**
|
||||
* Base AssertJ {@linkplain org.assertj.core.api.Assert assertions} that can be
|
||||
@@ -51,8 +46,8 @@ import org.springframework.util.function.ThrowingBiFunction;
|
||||
* extracting a part of the document for further {@linkplain JsonPathValueAssert
|
||||
* assertions} on the value.
|
||||
*
|
||||
* <p>Also supports comparing the JSON document against a target, using
|
||||
* {@linkplain JSONCompare JSON Assert}. Resources that are loaded from
|
||||
* <p>Also supports comparing the JSON document against a target, using a
|
||||
* {@linkplain JsonComparator JSON Comparator}. Resources that are loaded from
|
||||
* the classpath can be relative if a {@linkplain #withResourceLoadClass(Class)
|
||||
* class} is provided. By default, {@code UTF-8} is used to load resources,
|
||||
* but this can be overridden using {@link #withCharset(Charset)}.
|
||||
@@ -154,9 +149,9 @@ public abstract class AbstractJsonContentAssert<SELF extends AbstractJsonContent
|
||||
* the expected JSON
|
||||
* @param compareMode the compare mode used when checking
|
||||
*/
|
||||
public SELF isEqualTo(@Nullable CharSequence expected, JSONCompareMode compareMode) {
|
||||
public SELF isEqualTo(@Nullable CharSequence expected, JsonCompareMode compareMode) {
|
||||
String expectedJson = this.jsonLoader.getJson(expected);
|
||||
return assertNotFailed(compare(expectedJson, compareMode));
|
||||
return assertIsMatch(compare(expectedJson, compareMode));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -171,9 +166,9 @@ public abstract class AbstractJsonContentAssert<SELF extends AbstractJsonContent
|
||||
* @param expected a resource containing the expected JSON
|
||||
* @param compareMode the compare mode used when checking
|
||||
*/
|
||||
public SELF isEqualTo(Resource expected, JSONCompareMode compareMode) {
|
||||
public SELF isEqualTo(Resource expected, JsonCompareMode compareMode) {
|
||||
String expectedJson = this.jsonLoader.getJson(expected);
|
||||
return assertNotFailed(compare(expectedJson, compareMode));
|
||||
return assertIsMatch(compare(expectedJson, compareMode));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -184,9 +179,9 @@ public abstract class AbstractJsonContentAssert<SELF extends AbstractJsonContent
|
||||
* the expected JSON
|
||||
* @param comparator the comparator used when checking
|
||||
*/
|
||||
public SELF isEqualTo(@Nullable CharSequence expected, JSONComparator comparator) {
|
||||
public SELF isEqualTo(@Nullable CharSequence expected, JsonComparator comparator) {
|
||||
String expectedJson = this.jsonLoader.getJson(expected);
|
||||
return assertNotFailed(compare(expectedJson, comparator));
|
||||
return assertIsMatch(compare(expectedJson, comparator));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -201,13 +196,13 @@ public abstract class AbstractJsonContentAssert<SELF extends AbstractJsonContent
|
||||
* @param expected a resource containing the expected JSON
|
||||
* @param comparator the comparator used when checking
|
||||
*/
|
||||
public SELF isEqualTo(Resource expected, JSONComparator comparator) {
|
||||
public SELF isEqualTo(Resource expected, JsonComparator comparator) {
|
||||
String expectedJson = this.jsonLoader.getJson(expected);
|
||||
return assertNotFailed(compare(expectedJson, comparator));
|
||||
return assertIsMatch(compare(expectedJson, comparator));
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the actual value is {@link JSONCompareMode#LENIENT leniently}
|
||||
* Verify that the actual value is {@link JsonCompareMode#LENIENT leniently}
|
||||
* equal to the given JSON. The {@code expected} value can contain the JSON
|
||||
* itself or, if it ends with {@code .json}, the name of a resource to be
|
||||
* loaded from the classpath.
|
||||
@@ -215,11 +210,11 @@ public abstract class AbstractJsonContentAssert<SELF extends AbstractJsonContent
|
||||
* the expected JSON
|
||||
*/
|
||||
public SELF isLenientlyEqualTo(@Nullable CharSequence expected) {
|
||||
return isEqualTo(expected, JSONCompareMode.LENIENT);
|
||||
return isEqualTo(expected, JsonCompareMode.LENIENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the actual value is {@link JSONCompareMode#LENIENT leniently}
|
||||
* Verify that the actual value is {@link JsonCompareMode#LENIENT leniently}
|
||||
* equal to the given JSON {@link Resource}.
|
||||
* <p>The resource abstraction allows to provide several input types:
|
||||
* <ul>
|
||||
@@ -231,11 +226,11 @@ public abstract class AbstractJsonContentAssert<SELF extends AbstractJsonContent
|
||||
* @param expected a resource containing the expected JSON
|
||||
*/
|
||||
public SELF isLenientlyEqualTo(Resource expected) {
|
||||
return isEqualTo(expected, JSONCompareMode.LENIENT);
|
||||
return isEqualTo(expected, JsonCompareMode.LENIENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the actual value is {@link JSONCompareMode#STRICT strictly}
|
||||
* Verify that the actual value is {@link JsonCompareMode#STRICT strictly}
|
||||
* equal to the given JSON. The {@code expected} value can contain the JSON
|
||||
* itself or, if it ends with {@code .json}, the name of a resource to be
|
||||
* loaded from the classpath.
|
||||
@@ -243,11 +238,11 @@ public abstract class AbstractJsonContentAssert<SELF extends AbstractJsonContent
|
||||
* the expected JSON
|
||||
*/
|
||||
public SELF isStrictlyEqualTo(@Nullable CharSequence expected) {
|
||||
return isEqualTo(expected, JSONCompareMode.STRICT);
|
||||
return isEqualTo(expected, JsonCompareMode.STRICT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the actual value is {@link JSONCompareMode#STRICT strictly}
|
||||
* Verify that the actual value is {@link JsonCompareMode#STRICT strictly}
|
||||
* equal to the given JSON {@link Resource}.
|
||||
* <p>The resource abstraction allows to provide several input types:
|
||||
* <ul>
|
||||
@@ -259,7 +254,7 @@ public abstract class AbstractJsonContentAssert<SELF extends AbstractJsonContent
|
||||
* @param expected a resource containing the expected JSON
|
||||
*/
|
||||
public SELF isStrictlyEqualTo(Resource expected) {
|
||||
return isEqualTo(expected, JSONCompareMode.STRICT);
|
||||
return isEqualTo(expected, JsonCompareMode.STRICT);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -270,9 +265,9 @@ public abstract class AbstractJsonContentAssert<SELF extends AbstractJsonContent
|
||||
* the expected JSON
|
||||
* @param compareMode the compare mode used when checking
|
||||
*/
|
||||
public SELF isNotEqualTo(@Nullable CharSequence expected, JSONCompareMode compareMode) {
|
||||
public SELF isNotEqualTo(@Nullable CharSequence expected, JsonCompareMode compareMode) {
|
||||
String expectedJson = this.jsonLoader.getJson(expected);
|
||||
return assertNotPassed(compare(expectedJson, compareMode));
|
||||
return assertIsMismatch(compare(expectedJson, compareMode));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -287,9 +282,9 @@ public abstract class AbstractJsonContentAssert<SELF extends AbstractJsonContent
|
||||
* @param expected a resource containing the expected JSON
|
||||
* @param compareMode the compare mode used when checking
|
||||
*/
|
||||
public SELF isNotEqualTo(Resource expected, JSONCompareMode compareMode) {
|
||||
public SELF isNotEqualTo(Resource expected, JsonCompareMode compareMode) {
|
||||
String expectedJson = this.jsonLoader.getJson(expected);
|
||||
return assertNotPassed(compare(expectedJson, compareMode));
|
||||
return assertIsMismatch(compare(expectedJson, compareMode));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -300,9 +295,9 @@ public abstract class AbstractJsonContentAssert<SELF extends AbstractJsonContent
|
||||
* the expected JSON
|
||||
* @param comparator the comparator used when checking
|
||||
*/
|
||||
public SELF isNotEqualTo(@Nullable CharSequence expected, JSONComparator comparator) {
|
||||
public SELF isNotEqualTo(@Nullable CharSequence expected, JsonComparator comparator) {
|
||||
String expectedJson = this.jsonLoader.getJson(expected);
|
||||
return assertNotPassed(compare(expectedJson, comparator));
|
||||
return assertIsMismatch(compare(expectedJson, comparator));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -317,13 +312,13 @@ public abstract class AbstractJsonContentAssert<SELF extends AbstractJsonContent
|
||||
* @param expected a resource containing the expected JSON
|
||||
* @param comparator the comparator used when checking
|
||||
*/
|
||||
public SELF isNotEqualTo(Resource expected, JSONComparator comparator) {
|
||||
public SELF isNotEqualTo(Resource expected, JsonComparator comparator) {
|
||||
String expectedJson = this.jsonLoader.getJson(expected);
|
||||
return assertNotPassed(compare(expectedJson, comparator));
|
||||
return assertIsMismatch(compare(expectedJson, comparator));
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the actual value is not {@link JSONCompareMode#LENIENT
|
||||
* Verify that the actual value is not {@link JsonCompareMode#LENIENT
|
||||
* leniently} equal to the given JSON. The {@code expected} value can
|
||||
* contain the JSON itself or, if it ends with {@code .json}, the name of a
|
||||
* resource to be loaded from the classpath.
|
||||
@@ -331,11 +326,11 @@ public abstract class AbstractJsonContentAssert<SELF extends AbstractJsonContent
|
||||
* the expected JSON
|
||||
*/
|
||||
public SELF isNotLenientlyEqualTo(@Nullable CharSequence expected) {
|
||||
return isNotEqualTo(expected, JSONCompareMode.LENIENT);
|
||||
return isNotEqualTo(expected, JsonCompareMode.LENIENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the actual value is not {@link JSONCompareMode#LENIENT
|
||||
* Verify that the actual value is not {@link JsonCompareMode#LENIENT
|
||||
* leniently} equal to the given JSON {@link Resource}.
|
||||
* <p>The resource abstraction allows to provide several input types:
|
||||
* <ul>
|
||||
@@ -347,11 +342,11 @@ public abstract class AbstractJsonContentAssert<SELF extends AbstractJsonContent
|
||||
* @param expected a resource containing the expected JSON
|
||||
*/
|
||||
public SELF isNotLenientlyEqualTo(Resource expected) {
|
||||
return isNotEqualTo(expected, JSONCompareMode.LENIENT);
|
||||
return isNotEqualTo(expected, JsonCompareMode.LENIENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the actual value is not {@link JSONCompareMode#STRICT
|
||||
* Verify that the actual value is not {@link JsonCompareMode#STRICT
|
||||
* strictly} equal to the given JSON. The {@code expected} value can
|
||||
* contain the JSON itself or, if it ends with {@code .json}, the name of a
|
||||
* resource to be loaded from the classpath.
|
||||
@@ -359,11 +354,11 @@ public abstract class AbstractJsonContentAssert<SELF extends AbstractJsonContent
|
||||
* the expected JSON
|
||||
*/
|
||||
public SELF isNotStrictlyEqualTo(@Nullable CharSequence expected) {
|
||||
return isNotEqualTo(expected, JSONCompareMode.STRICT);
|
||||
return isNotEqualTo(expected, JsonCompareMode.STRICT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the actual value is not {@link JSONCompareMode#STRICT
|
||||
* Verify that the actual value is not {@link JsonCompareMode#STRICT
|
||||
* strictly} equal to the given JSON {@link Resource}.
|
||||
* <p>The resource abstraction allows to provide several input types:
|
||||
* <ul>
|
||||
@@ -375,7 +370,7 @@ public abstract class AbstractJsonContentAssert<SELF extends AbstractJsonContent
|
||||
* @param expected a resource containing the expected JSON
|
||||
*/
|
||||
public SELF isNotStrictlyEqualTo(Resource expected) {
|
||||
return isNotEqualTo(expected, JSONCompareMode.STRICT);
|
||||
return isNotEqualTo(expected, JsonCompareMode.STRICT);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -405,54 +400,25 @@ public abstract class AbstractJsonContentAssert<SELF extends AbstractJsonContent
|
||||
}
|
||||
|
||||
|
||||
private JSONCompareResult compare(@Nullable CharSequence expectedJson, JSONCompareMode compareMode) {
|
||||
return compare(this.actual, expectedJson, (actualJsonString, expectedJsonString) ->
|
||||
JSONCompare.compareJSON(expectedJsonString, actualJsonString, compareMode));
|
||||
private JsonComparison compare(@Nullable CharSequence expectedJson, JsonCompareMode compareMode) {
|
||||
return compare(expectedJson, JsonAssert.comparator(compareMode));
|
||||
}
|
||||
|
||||
private JSONCompareResult compare(@Nullable CharSequence expectedJson, JSONComparator comparator) {
|
||||
return compare(this.actual, expectedJson, (actualJsonString, expectedJsonString) ->
|
||||
JSONCompare.compareJSON(expectedJsonString, actualJsonString, comparator));
|
||||
private JsonComparison compare(@Nullable CharSequence expectedJson, JsonComparator comparator) {
|
||||
return comparator.compare((expectedJson != null) ? expectedJson.toString() : null, this.actual);
|
||||
}
|
||||
|
||||
private JSONCompareResult compare(@Nullable CharSequence actualJson, @Nullable CharSequence expectedJson,
|
||||
ThrowingBiFunction<String, String, JSONCompareResult> comparator) {
|
||||
|
||||
if (actualJson == null) {
|
||||
return compareForNull(expectedJson);
|
||||
}
|
||||
if (expectedJson == null) {
|
||||
return compareForNull(actualJson.toString());
|
||||
}
|
||||
try {
|
||||
return comparator.applyWithException(actualJson.toString(), expectedJson.toString());
|
||||
}
|
||||
catch (Exception ex) {
|
||||
if (ex instanceof RuntimeException runtimeException) {
|
||||
throw runtimeException;
|
||||
}
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
private SELF assertIsMatch(JsonComparison result) {
|
||||
return assertComparison(result, JsonComparison.Result.MATCH);
|
||||
}
|
||||
|
||||
private JSONCompareResult compareForNull(@Nullable CharSequence expectedJson) {
|
||||
JSONCompareResult result = new JSONCompareResult();
|
||||
if (expectedJson != null) {
|
||||
result.fail("Expected null JSON");
|
||||
}
|
||||
return result;
|
||||
private SELF assertIsMismatch(JsonComparison result) {
|
||||
return assertComparison(result, JsonComparison.Result.MISMATCH);
|
||||
}
|
||||
|
||||
private SELF assertNotFailed(JSONCompareResult result) {
|
||||
if (result.failed()) {
|
||||
failWithMessage("JSON comparison failure: %s", result.getMessage());
|
||||
}
|
||||
return this.myself;
|
||||
}
|
||||
|
||||
private SELF assertNotPassed(JSONCompareResult result) {
|
||||
if (result.passed()) {
|
||||
failWithMessage("JSON comparison failure: %s", result.getMessage());
|
||||
private SELF assertComparison(JsonComparison jsonComparison, JsonComparison.Result requiredResult) {
|
||||
if (jsonComparison.getResult() != requiredResult) {
|
||||
failWithMessage("JSON comparison failure: %s", jsonComparison.getMessage());
|
||||
}
|
||||
return this.myself;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright 2002-2024 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.test.json;
|
||||
|
||||
import org.skyscreamer.jsonassert.JSONCompare;
|
||||
import org.skyscreamer.jsonassert.JSONCompareMode;
|
||||
import org.skyscreamer.jsonassert.JSONCompareResult;
|
||||
import org.skyscreamer.jsonassert.comparator.JSONComparator;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.function.ThrowingBiFunction;
|
||||
|
||||
/**
|
||||
* Useful methods that can be used with {@code org.skyscreamer.jsonassert}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 6.2
|
||||
*/
|
||||
public abstract class JsonAssert {
|
||||
|
||||
/**
|
||||
* Create a {@link JsonComparator} from the given {@link JsonCompareMode}.
|
||||
* @param compareMode the mode to use
|
||||
* @return a new {@link JsonComparator} instance
|
||||
* @see JSONCompareMode#STRICT
|
||||
* @see JSONCompareMode#LENIENT
|
||||
*/
|
||||
public static JsonComparator comparator(JsonCompareMode compareMode) {
|
||||
return comparator(toJSONCompareMode(compareMode));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link JsonComparator} from the given JSONAssert
|
||||
* {@link JSONComparator}.
|
||||
* @param comparator the JSON Assert {@link JSONComparator}
|
||||
* @return a new {@link JsonComparator} instance
|
||||
*/
|
||||
public static JsonComparator comparator(JSONComparator comparator) {
|
||||
return comparator((expectedJson, actualJson) -> JSONCompare
|
||||
.compareJSON(expectedJson, actualJson, comparator));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link JsonComparator} from the given JSONAssert
|
||||
* {@link JSONCompareMode}.
|
||||
* @param mode the JSON Assert {@link JSONCompareMode}
|
||||
* @return a new {@link JsonComparator} instance
|
||||
*/
|
||||
public static JsonComparator comparator(JSONCompareMode mode) {
|
||||
return comparator((expectedJson, actualJson) -> JSONCompare
|
||||
.compareJSON(expectedJson, actualJson, mode));
|
||||
}
|
||||
|
||||
private static JsonComparator comparator(ThrowingBiFunction<String, String, JSONCompareResult> compareFunction) {
|
||||
return (expectedJson, actualJson) -> compare(expectedJson, actualJson, compareFunction);
|
||||
}
|
||||
|
||||
private static JsonComparison compare(@Nullable String expectedJson, @Nullable String actualJson,
|
||||
ThrowingBiFunction<String, String, JSONCompareResult> compareFunction) {
|
||||
|
||||
if (actualJson == null) {
|
||||
return (expectedJson != null)
|
||||
? JsonComparison.mismatch("Expected null JSON")
|
||||
: JsonComparison.match();
|
||||
}
|
||||
if (expectedJson == null) {
|
||||
return JsonComparison.mismatch("Expected non-null JSON");
|
||||
}
|
||||
JSONCompareResult result = compareFunction.throwing(IllegalStateException::new).apply(expectedJson, actualJson);
|
||||
return (!result.passed())
|
||||
? JsonComparison.mismatch(result.getMessage())
|
||||
: JsonComparison.match();
|
||||
}
|
||||
|
||||
private static JSONCompareMode toJSONCompareMode(JsonCompareMode compareMode) {
|
||||
return (compareMode != JsonCompareMode.LENIENT ? JSONCompareMode.STRICT : JSONCompareMode.LENIENT);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2002-2024 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.test.json;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.test.json.JsonComparison.Result;
|
||||
|
||||
/**
|
||||
* Strategy interface used to compare JSON strings.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 6.2
|
||||
* @see JsonAssert
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface JsonComparator {
|
||||
|
||||
/**
|
||||
* Compare the given JSON strings.
|
||||
* @param expectedJson the expected JSON
|
||||
* @param actualJson the actual JSON
|
||||
* @return the JSON comparison
|
||||
*/
|
||||
JsonComparison compare(@Nullable String expectedJson, @Nullable String actualJson);
|
||||
|
||||
/**
|
||||
* Assert that the {@code expectedJson} matches the comparison rules of ths
|
||||
* instance against the {@code actualJson}. Throw an {@link AssertionError}
|
||||
* if the comparison does not match.
|
||||
* @param expectedJson the expected JSON
|
||||
* @param actualJson the actual JSON
|
||||
*/
|
||||
default void assertIsMatch(@Nullable String expectedJson, @Nullable String actualJson) {
|
||||
JsonComparison comparison = compare(expectedJson, actualJson);
|
||||
if (comparison.getResult() == Result.MISMATCH) {
|
||||
throw new AssertionError(comparison.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2002-2024 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.test.json;
|
||||
|
||||
/**
|
||||
* Modes that can be used to compare JSON.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 6.2
|
||||
*/
|
||||
public enum JsonCompareMode {
|
||||
|
||||
/**
|
||||
* Strict checking.
|
||||
*/
|
||||
STRICT,
|
||||
|
||||
/**
|
||||
* Lenient checking.
|
||||
*/
|
||||
LENIENT
|
||||
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright 2002-2024 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.test.json;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* A comparison of two JSON strings as returned from a {@link JsonComparator}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 6.2
|
||||
*/
|
||||
public final class JsonComparison {
|
||||
|
||||
private final Result result;
|
||||
|
||||
@Nullable
|
||||
private final String message;
|
||||
|
||||
|
||||
private JsonComparison(Result result, @Nullable String message) {
|
||||
this.result = result;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to create a new {@link JsonComparison} when the JSON
|
||||
* strings match.
|
||||
* @return a new {@link JsonComparison} instance
|
||||
*/
|
||||
public static JsonComparison match() {
|
||||
return new JsonComparison(Result.MATCH, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to create a new {@link JsonComparison} when the JSON strings
|
||||
* do not match.
|
||||
* @param message a message describing the mismatch
|
||||
* @return a new {@link JsonComparison} instance
|
||||
*/
|
||||
public static JsonComparison mismatch(String message) {
|
||||
return new JsonComparison(Result.MISMATCH, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the result of the comparison.
|
||||
*/
|
||||
public Result getResult() {
|
||||
return this.result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a message describing the comparison.
|
||||
*/
|
||||
@Nullable
|
||||
public String getMessage() {
|
||||
return this.message;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Comparison results.
|
||||
*/
|
||||
public enum Result {
|
||||
|
||||
/**
|
||||
* The JSON strings match when considering the comparison rules.
|
||||
*/
|
||||
MATCH,
|
||||
|
||||
/**
|
||||
* The JSON strings do not match when considering the comparison rules.
|
||||
*/
|
||||
MISMATCH
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,6 +18,8 @@ package org.springframework.test.util;
|
||||
|
||||
import org.skyscreamer.jsonassert.JSONAssert;
|
||||
|
||||
import org.springframework.test.json.JsonComparator;
|
||||
|
||||
/**
|
||||
* A helper class for assertions on JSON content.
|
||||
*
|
||||
@@ -26,7 +28,10 @@ import org.skyscreamer.jsonassert.JSONAssert;
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @since 4.1
|
||||
* @deprecated in favor of using {@link JSONAssert} directly or the
|
||||
* {@link JsonComparator} abstraction
|
||||
*/
|
||||
@Deprecated(since = "6.2")
|
||||
public class JsonExpectationsHelper {
|
||||
|
||||
/**
|
||||
|
||||
@@ -42,7 +42,10 @@ import org.springframework.http.converter.FormHttpMessageConverter;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.mock.http.MockHttpInputMessage;
|
||||
import org.springframework.mock.http.client.MockClientHttpRequest;
|
||||
import org.springframework.test.util.JsonExpectationsHelper;
|
||||
import org.springframework.test.json.JsonAssert;
|
||||
import org.springframework.test.json.JsonComparator;
|
||||
import org.springframework.test.json.JsonCompareMode;
|
||||
import org.springframework.test.json.JsonComparison;
|
||||
import org.springframework.test.util.XmlExpectationsHelper;
|
||||
import org.springframework.test.web.client.RequestMatcher;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
@@ -71,8 +74,6 @@ public class ContentRequestMatchers {
|
||||
|
||||
private final XmlExpectationsHelper xmlHelper;
|
||||
|
||||
private final JsonExpectationsHelper jsonHelper;
|
||||
|
||||
|
||||
/**
|
||||
* Class constructor, not for direct instantiation.
|
||||
@@ -80,7 +81,6 @@ public class ContentRequestMatchers {
|
||||
*/
|
||||
protected ContentRequestMatchers() {
|
||||
this.xmlHelper = new XmlExpectationsHelper();
|
||||
this.jsonHelper = new JsonExpectationsHelper();
|
||||
}
|
||||
|
||||
|
||||
@@ -331,7 +331,7 @@ public class ContentRequestMatchers {
|
||||
* @since 5.0.5
|
||||
*/
|
||||
public RequestMatcher json(String expectedJsonContent) {
|
||||
return json(expectedJsonContent, false);
|
||||
return json(expectedJsonContent, JsonCompareMode.LENIENT);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -348,12 +348,43 @@ public class ContentRequestMatchers {
|
||||
* @param expectedJsonContent the expected JSON content
|
||||
* @param strict enables strict checking
|
||||
* @since 5.0.5
|
||||
* @deprecated in favor of {@link #json(String, JsonCompareMode)}
|
||||
*/
|
||||
@Deprecated(since = "6.2")
|
||||
public RequestMatcher json(String expectedJsonContent, boolean strict) {
|
||||
JsonCompareMode compareMode = (strict ? JsonCompareMode.STRICT : JsonCompareMode.LENIENT);
|
||||
return json(expectedJsonContent, compareMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the request body and the given string as JSON and assert the two
|
||||
* using the given {@linkplain JsonCompareMode mode}. If the comparison failed,
|
||||
* throws an {@link AssertionError} with the message of the {@link JsonComparison}.
|
||||
* <p>Use of this matcher requires the <a
|
||||
* href="https://jsonassert.skyscreamer.org/">JSONassert</a> library.
|
||||
* @param expectedJsonContent the expected JSON content
|
||||
* @param compareMode the compare mode
|
||||
* @since 6.2
|
||||
*/
|
||||
public RequestMatcher json(String expectedJsonContent, JsonCompareMode compareMode) {
|
||||
return json(expectedJsonContent, JsonAssert.comparator(compareMode));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the request body and the given string as JSON and assert the two
|
||||
* using the given {@link JsonComparator}. If the comparison failed, throws an
|
||||
* {@link AssertionError} with the message of the {@link JsonComparison}.
|
||||
* <p>Use this matcher if you require a custom JSONAssert configuration or
|
||||
* if you desire to use another assertion library.
|
||||
* @param expectedJsonContent the expected JSON content
|
||||
* @param comparator the comparator to use
|
||||
* @since 6.2
|
||||
*/
|
||||
public RequestMatcher json(String expectedJsonContent, JsonComparator comparator) {
|
||||
return request -> {
|
||||
try {
|
||||
MockClientHttpRequest mockRequest = (MockClientHttpRequest) request;
|
||||
this.jsonHelper.assertJsonEqual(expectedJsonContent, mockRequest.getBodyAsString(), strict);
|
||||
comparator.assertIsMatch(expectedJsonContent, mockRequest.getBodyAsString());
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new AssertionError("Failed to parse expected or actual JSON request content", ex);
|
||||
|
||||
@@ -45,9 +45,11 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.http.client.reactive.ClientHttpConnector;
|
||||
import org.springframework.http.client.reactive.ClientHttpRequest;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.test.json.JsonAssert;
|
||||
import org.springframework.test.json.JsonComparator;
|
||||
import org.springframework.test.json.JsonCompareMode;
|
||||
import org.springframework.test.util.AssertionErrors;
|
||||
import org.springframework.test.util.ExceptionCollector;
|
||||
import org.springframework.test.util.JsonExpectationsHelper;
|
||||
import org.springframework.test.util.XmlExpectationsHelper;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
@@ -658,10 +660,22 @@ class DefaultWebTestClient implements WebTestClient {
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated(since = "6.2")
|
||||
public BodyContentSpec json(String json, boolean strict) {
|
||||
JsonCompareMode compareMode = (strict ? JsonCompareMode.STRICT : JsonCompareMode.LENIENT);
|
||||
return json(json, compareMode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BodyContentSpec json(String expectedJson, JsonCompareMode compareMode) {
|
||||
return json(expectedJson, JsonAssert.comparator(compareMode));
|
||||
}
|
||||
|
||||
@Override
|
||||
public BodyContentSpec json(String expectedJson, JsonComparator comparator) {
|
||||
this.result.assertWithDiagnostics(() -> {
|
||||
try {
|
||||
new JsonExpectationsHelper().assertJsonEqual(json, getBodyAsString(), strict);
|
||||
comparator.assertIsMatch(expectedJson, getBodyAsString());
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new AssertionError("JSON parsing error", ex);
|
||||
|
||||
@@ -40,6 +40,9 @@ import org.springframework.http.client.reactive.ClientHttpRequest;
|
||||
import org.springframework.http.codec.ClientCodecConfigurer;
|
||||
import org.springframework.http.codec.ServerCodecConfigurer;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.test.json.JsonComparator;
|
||||
import org.springframework.test.json.JsonCompareMode;
|
||||
import org.springframework.test.json.JsonComparison;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.validation.Validator;
|
||||
import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder;
|
||||
@@ -997,10 +1000,10 @@ public interface WebTestClient {
|
||||
* <a href="https://jsonassert.skyscreamer.org/">JSONassert</a> library
|
||||
* to be on the classpath.
|
||||
* @param expectedJson the expected JSON content
|
||||
* @see #json(String, boolean)
|
||||
* @see #json(String, JsonCompareMode)
|
||||
*/
|
||||
default BodyContentSpec json(String expectedJson) {
|
||||
return json(expectedJson, false);
|
||||
return json(expectedJson, JsonCompareMode.LENIENT);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1019,9 +1022,37 @@ public interface WebTestClient {
|
||||
* @param strict enables strict checking if {@code true}
|
||||
* @since 5.3.16
|
||||
* @see #json(String)
|
||||
* @deprecated in favor of {@link #json(String, JsonCompareMode)}
|
||||
*/
|
||||
@Deprecated(since = "6.2")
|
||||
BodyContentSpec json(String expectedJson, boolean strict);
|
||||
|
||||
/**
|
||||
* Parse the expected and actual response content as JSON and perform a
|
||||
* comparison using the given {@linkplain JsonCompareMode mode}. If the
|
||||
* comparison failed, throws an {@link AssertionError} with the message
|
||||
* of the {@link JsonComparison}.
|
||||
* <p>Use of this method requires the
|
||||
* <a href="https://jsonassert.skyscreamer.org/">JSONassert</a> library
|
||||
* to be on the classpath.
|
||||
* @param expectedJson the expected JSON content
|
||||
* @param compareMode the compare mode
|
||||
* @since 6.2
|
||||
* @see #json(String)
|
||||
*/
|
||||
BodyContentSpec json(String expectedJson, JsonCompareMode compareMode);
|
||||
|
||||
/**
|
||||
* Parse the expected and actual response content as JSON and perform a
|
||||
* comparison using the given {@link JsonComparator}. If the comparison
|
||||
* failed, throws an {@link AssertionError} with the message of the
|
||||
* {@link JsonComparison}.
|
||||
* @param expectedJson the expected JSON content
|
||||
* @param comparator the comparator to use
|
||||
* @since 6.2
|
||||
*/
|
||||
BodyContentSpec json(String expectedJson, JsonComparator comparator);
|
||||
|
||||
/**
|
||||
* Parse expected and actual response content as XML and assert that
|
||||
* the two are "similar", i.e. they contain the same elements and
|
||||
|
||||
@@ -28,7 +28,10 @@ import org.hamcrest.Matcher;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.util.JsonExpectationsHelper;
|
||||
import org.springframework.test.json.JsonAssert;
|
||||
import org.springframework.test.json.JsonComparator;
|
||||
import org.springframework.test.json.JsonCompareMode;
|
||||
import org.springframework.test.json.JsonComparison;
|
||||
import org.springframework.test.util.XmlExpectationsHelper;
|
||||
import org.springframework.test.web.servlet.ResultMatcher;
|
||||
|
||||
@@ -51,8 +54,6 @@ public class ContentResultMatchers {
|
||||
|
||||
private final XmlExpectationsHelper xmlHelper;
|
||||
|
||||
private final JsonExpectationsHelper jsonHelper;
|
||||
|
||||
|
||||
/**
|
||||
* Protected constructor.
|
||||
@@ -60,7 +61,6 @@ public class ContentResultMatchers {
|
||||
*/
|
||||
protected ContentResultMatchers() {
|
||||
this.xmlHelper = new XmlExpectationsHelper();
|
||||
this.jsonHelper = new JsonExpectationsHelper();
|
||||
}
|
||||
|
||||
|
||||
@@ -198,13 +198,16 @@ public class ContentResultMatchers {
|
||||
/**
|
||||
* Parse the expected and actual strings as JSON and assert the two
|
||||
* are "similar" - i.e. they contain the same attribute-value pairs
|
||||
* regardless of formatting with a lenient checking (extensible, and non-strict array
|
||||
* ordering).
|
||||
* regardless of formatting with a lenient checking (extensible,
|
||||
* and non-strict array ordering).
|
||||
* <p>Use of this matcher requires the <a
|
||||
* href="https://jsonassert.skyscreamer.org/">JSONassert</a> library.
|
||||
* @param jsonContent the expected JSON content
|
||||
* @since 4.1
|
||||
* @see #json(String, JsonCompareMode)
|
||||
*/
|
||||
public ResultMatcher json(String jsonContent) {
|
||||
return json(jsonContent, false);
|
||||
return json(jsonContent, JsonCompareMode.LENIENT);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -220,11 +223,42 @@ public class ContentResultMatchers {
|
||||
* @param jsonContent the expected JSON content
|
||||
* @param strict enables strict checking
|
||||
* @since 4.2
|
||||
* @deprecated in favor of {@link #json(String, JsonCompareMode)}
|
||||
*/
|
||||
@Deprecated(since = "6.2")
|
||||
public ResultMatcher json(String jsonContent, boolean strict) {
|
||||
JsonCompareMode compareMode = (strict ? JsonCompareMode.STRICT : JsonCompareMode.LENIENT);
|
||||
return json(jsonContent, compareMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the response content and the given string as JSON and assert the two
|
||||
* using the given {@linkplain JsonCompareMode mode}. If the comparison failed,
|
||||
* throws an {@link AssertionError} with the message of the {@link JsonComparison}.
|
||||
* <p>Use of this matcher requires the <a
|
||||
* href="https://jsonassert.skyscreamer.org/">JSONassert</a> library.
|
||||
* @param jsonContent the expected JSON content
|
||||
* @param compareMode the compare mode
|
||||
* @since 6.2
|
||||
*/
|
||||
public ResultMatcher json(String jsonContent, JsonCompareMode compareMode) {
|
||||
return json(jsonContent, JsonAssert.comparator(compareMode));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the response content and the given string as JSON and assert the two
|
||||
* using the given {@link JsonComparator}. If the comparison failed, throws an
|
||||
* {@link AssertionError} with the message of the {@link JsonComparison}.
|
||||
* <p>Use this matcher if you require a custom JSONAssert configuration or
|
||||
* if you desire to use another assertion library.
|
||||
* @param jsonContent the expected JSON content
|
||||
* @param comparator the comparator to use
|
||||
* @since 6.2
|
||||
*/
|
||||
public ResultMatcher json(String jsonContent, JsonComparator comparator) {
|
||||
return result -> {
|
||||
String content = result.getResponse().getContentAsString(StandardCharsets.UTF_8);
|
||||
this.jsonHelper.assertJsonEqual(jsonContent, content, strict);
|
||||
comparator.assertIsMatch(jsonContent, content);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user