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:
Phillip Webb
2024-05-20 11:08:20 -07:00
committed by Stéphane Nicoll
parent 80faa94afc
commit d8dcd3af0b
14 changed files with 539 additions and 118 deletions

View File

@@ -28,6 +28,8 @@ import java.util.stream.Stream;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.assertj.core.api.AssertProvider;
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
@@ -37,7 +39,7 @@ import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.skyscreamer.jsonassert.JSONCompareMode;
import org.skyscreamer.jsonassert.comparator.DefaultComparator;
import org.skyscreamer.jsonassert.JSONCompareResult;
import org.skyscreamer.jsonassert.comparator.JSONComparator;
import org.springframework.core.ParameterizedTypeReference;
@@ -55,6 +57,10 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.assertj.core.api.Assertions.entry;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link AbstractJsonContentAssert}.
@@ -79,7 +85,7 @@ class AbstractJsonContentAssertTests {
private static final MappingJackson2HttpMessageConverter jsonHttpMessageConverter =
new MappingJackson2HttpMessageConverter(new ObjectMapper());
private static final JSONComparator comparator = new DefaultComparator(JSONCompareMode.LENIENT);
private static final JsonComparator comparator = JsonAssert.comparator(JsonCompareMode.LENIENT);
@Test
void isNullWhenActualIsNullShouldPass() {
@@ -364,29 +370,29 @@ class AbstractJsonContentAssertTests {
void isEqualToWhenExpectedIsNullShouldFail() {
CharSequence actual = null;
assertThatExceptionOfType(AssertionError.class)
.isThrownBy(() -> assertThat(forJson(SOURCE)).isEqualTo(actual, JSONCompareMode.LENIENT));
.isThrownBy(() -> assertThat(forJson(SOURCE)).isEqualTo(actual, JsonCompareMode.LENIENT));
}
@Test
void isEqualToWhenStringIsMatchingAndLenientShouldPass() {
assertThat(forJson(SOURCE)).isEqualTo(LENIENT_SAME, JSONCompareMode.LENIENT);
assertThat(forJson(SOURCE)).isEqualTo(LENIENT_SAME, JsonCompareMode.LENIENT);
}
@Test
void isEqualToWhenStringIsNotMatchingAndLenientShouldFail() {
assertThatExceptionOfType(AssertionError.class)
.isThrownBy(() -> assertThat(forJson(SOURCE)).isEqualTo(DIFFERENT, JSONCompareMode.LENIENT));
.isThrownBy(() -> assertThat(forJson(SOURCE)).isEqualTo(DIFFERENT, JsonCompareMode.LENIENT));
}
@Test
void isEqualToWhenResourcePathIsMatchingAndLenientShouldPass() {
assertThat(forJson(SOURCE)).isEqualTo("lenient-same.json", JSONCompareMode.LENIENT);
assertThat(forJson(SOURCE)).isEqualTo("lenient-same.json", JsonCompareMode.LENIENT);
}
@Test
void isEqualToWhenResourcePathIsNotMatchingAndLenientShouldFail() {
assertThatExceptionOfType(AssertionError.class)
.isThrownBy(() -> assertThat(forJson(SOURCE)).isEqualTo("different.json", JSONCompareMode.LENIENT));
.isThrownBy(() -> assertThat(forJson(SOURCE)).isEqualTo("different.json", JsonCompareMode.LENIENT));
}
Stream<Arguments> source() {
@@ -416,14 +422,14 @@ class AbstractJsonContentAssertTests {
@ParameterizedTest
@MethodSource("lenientSame")
void isEqualToWhenResourceIsMatchingAndLenientSameShouldPass(Resource expected) {
assertThat(forJson(SOURCE)).isEqualTo(expected, JSONCompareMode.LENIENT);
assertThat(forJson(SOURCE)).isEqualTo(expected, JsonCompareMode.LENIENT);
}
@ParameterizedTest
@MethodSource("different")
void isEqualToWhenResourceIsNotMatchingAndLenientShouldFail(Resource expected) {
assertThatExceptionOfType(AssertionError.class).isThrownBy(
() -> assertThat(forJson(SOURCE)).isEqualTo(expected, JSONCompareMode.LENIENT));
() -> assertThat(forJson(SOURCE)).isEqualTo(expected, JsonCompareMode.LENIENT));
}
@Test
@@ -568,36 +574,36 @@ class AbstractJsonContentAssertTests {
@Test
void isNotEqualToWhenStringIsMatchingAndLenientShouldFail() {
assertThatExceptionOfType(AssertionError.class)
.isThrownBy(() -> assertThat(forJson(SOURCE)).isNotEqualTo(LENIENT_SAME, JSONCompareMode.LENIENT));
.isThrownBy(() -> assertThat(forJson(SOURCE)).isNotEqualTo(LENIENT_SAME, JsonCompareMode.LENIENT));
}
@Test
void isNotEqualToWhenStringIsNotMatchingAndLenientShouldPass() {
assertThat(forJson(SOURCE)).isNotEqualTo(DIFFERENT, JSONCompareMode.LENIENT);
assertThat(forJson(SOURCE)).isNotEqualTo(DIFFERENT, JsonCompareMode.LENIENT);
}
@Test
void isNotEqualToWhenResourcePathIsMatchingAndLenientShouldFail() {
assertThatExceptionOfType(AssertionError.class).isThrownBy(
() -> assertThat(forJson(SOURCE)).isNotEqualTo("lenient-same.json", JSONCompareMode.LENIENT));
() -> assertThat(forJson(SOURCE)).isNotEqualTo("lenient-same.json", JsonCompareMode.LENIENT));
}
@Test
void isNotEqualToWhenResourcePathIsNotMatchingAndLenientShouldPass() {
assertThat(forJson(SOURCE)).isNotEqualTo("different.json", JSONCompareMode.LENIENT);
assertThat(forJson(SOURCE)).isNotEqualTo("different.json", JsonCompareMode.LENIENT);
}
@ParameterizedTest
@MethodSource("lenientSame")
void isNotEqualToWhenResourceIsMatchingAndLenientShouldFail(Resource expected) {
assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> assertThat(forJson(SOURCE))
.isNotEqualTo(expected, JSONCompareMode.LENIENT));
.isNotEqualTo(expected, JsonCompareMode.LENIENT));
}
@ParameterizedTest
@MethodSource("different")
void isNotEqualToWhenResourceIsNotMatchingAndLenientShouldPass(Resource expected) {
assertThat(forJson(SOURCE)).isNotEqualTo(expected, JSONCompareMode.LENIENT);
assertThat(forJson(SOURCE)).isNotEqualTo(expected, JsonCompareMode.LENIENT);
}
@Test
@@ -715,6 +721,28 @@ class AbstractJsonContentAssertTests {
assertThat(forJson(SOURCE)).isNotStrictlyEqualTo(expected);
}
@Test
void isEqualToWithCustomCompareMode() {
String differentOrder = """
{
"spring": [
"framework",
"boot"
]
}
""";
assertThat(forJson(SOURCE)).isEqualTo(differentOrder, JsonAssert.comparator(JSONCompareMode.NON_EXTENSIBLE));
}
@Test
void isEqualToWithCustomJsonComparator() throws JSONException {
String empty = "{}";
JSONComparator comparator = mock(JSONComparator.class);
given(comparator.compareJSON(any(JSONObject.class), any(JSONObject.class))).willReturn(new JSONCompareResult());
assertThat(forJson(SOURCE)).isEqualTo(empty, JsonAssert.comparator(comparator));
verify(comparator).compareJSON(any(JSONObject.class), any(JSONObject.class));
}
@Test
void withResourceLoadClassShouldAllowToLoadRelativeContent() {
AbstractJsonContentAssert<?> jsonAssert = assertThat(forJson(NULLS)).withResourceLoadClass(String.class);
@@ -730,6 +758,31 @@ class AbstractJsonContentAssertTests {
}
}
@Nested
class JsonComparatorTests {
private final JsonComparator comparator = mock(JsonComparator.class);
@Test
void isEqualToInvokesComparator() {
given(comparator.compare("{ }", "{}")).willReturn(JsonComparison.match());
assertThat(forJson("{}")).isEqualTo("{ }", this.comparator);
verify(comparator).compare("{ }", "{}");
}
@Test
void isEqualToWithNoMatchProvidesErrorMessage() {
given(comparator.compare("{ }", "{}")).willReturn(JsonComparison.mismatch("No additional whitespace expected"));
assertThatExceptionOfType(AssertionError.class)
.isThrownBy(() -> assertThat(forJson("{}")).isEqualTo("{ }", this.comparator))
.withMessageContaining("No additional whitespace expected");
verify(comparator).compare("{ }", "{}");
}
private AssertProvider<AbstractJsonContentAssert<?>> forJson(@Nullable String json) {
return () -> new TestJsonContentAssert(json, null).withResourceLoadClass(getClass());
}
}
private Consumer<AssertionError> hasFailedToMatchPath(String expression) {
return error -> assertThat(error.getMessage()).containsSubsequence("Expecting:",

View File

@@ -23,6 +23,7 @@ import reactor.core.publisher.Flux;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.test.json.JsonCompareMode;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@@ -74,7 +75,7 @@ class JsonContentTests {
{"firstName":"John", "lastName":"Smith"}
]
""",
true);
JsonCompareMode.STRICT);
}
@Test
@@ -89,7 +90,7 @@ class JsonContentTests {
{"firstName":"John"}
]
""",
true)
JsonCompareMode.STRICT)
);
}