Skip to content

Commit e2c5887

Browse files
committed
Allow JSON content assertions to be nested
Previously, AbstractJsonContentAssert worked on a raw String, which made standard AssertJ nested calls, such as satisfies, to return an assert on the raw string, rather than one with JSON support. This commit rework AbstractJsonContentAssert so that it no longer extend from AbstractStringAssert. This makes the list of methods more focused on JSON assertions, and allow standard operations to provide the right assert object. Closes gh-32894
1 parent 489d18a commit e2c5887

File tree

6 files changed

+100
-35
lines changed

6 files changed

+100
-35
lines changed

spring-test/src/main/java/org/springframework/test/json/AbstractJsonContentAssert.java

+48-15
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,9 @@
2424

2525
import com.jayway.jsonpath.JsonPath;
2626
import com.jayway.jsonpath.PathNotFoundException;
27-
import org.assertj.core.api.AbstractStringAssert;
27+
import org.assertj.core.api.AbstractObjectAssert;
2828
import org.assertj.core.api.AssertProvider;
29+
import org.assertj.core.api.Assertions;
2930
import org.assertj.core.error.BasicErrorMessageFactory;
3031
import org.assertj.core.internal.Failures;
3132

@@ -61,7 +62,7 @@
6162
* @param <SELF> the type of assertions
6263
*/
6364
public abstract class AbstractJsonContentAssert<SELF extends AbstractJsonContentAssert<SELF>>
64-
extends AbstractStringAssert<SELF> {
65+
extends AbstractObjectAssert<SELF, JsonContent> {
6566

6667
private static final Failures failures = Failures.instance();
6768

@@ -79,16 +80,12 @@ public abstract class AbstractJsonContentAssert<SELF extends AbstractJsonContent
7980

8081
/**
8182
* Create an assert for the given JSON document.
82-
* <p>Path can be converted to a value object using the given
83-
* {@linkplain GenericHttpMessageConverter JSON message converter}.
84-
* @param json the JSON document to assert
85-
* @param jsonMessageConverter the converter to use
83+
* @param actual the JSON document to assert
8684
* @param selfType the implementation type of this assert
8785
*/
88-
protected AbstractJsonContentAssert(@Nullable String json,
89-
@Nullable GenericHttpMessageConverter<Object> jsonMessageConverter, Class<?> selfType) {
90-
super(json, selfType);
91-
this.jsonMessageConverter = jsonMessageConverter;
86+
protected AbstractJsonContentAssert(@Nullable JsonContent actual, Class<?> selfType) {
87+
super(actual, selfType);
88+
this.jsonMessageConverter = (actual != null ? actual.getJsonMessageConverter() : null);
9289
this.jsonLoader = new JsonLoader(null, null);
9390
as("JSON content");
9491
}
@@ -141,6 +138,19 @@ public SELF doesNotHavePath(String path) {
141138

142139
// JsonAssert support
143140

141+
/**
142+
* Verify that the actual value is {@linkplain JsonCompareMode#STRICT strictly}
143+
* equal to the given JSON. The {@code expected} value can contain the JSON
144+
* itself or, if it ends with {@code .json}, the name of a resource to be
145+
* loaded from the classpath.
146+
* @param expected the expected JSON or the name of a resource containing
147+
* the expected JSON
148+
* @see #isEqualTo(CharSequence, JsonCompareMode)
149+
*/
150+
public SELF isEqualTo(@Nullable CharSequence expected) {
151+
return isEqualTo(expected, JsonCompareMode.STRICT);
152+
}
153+
144154
/**
145155
* Verify that the actual value is equal to the given JSON. The
146156
* {@code expected} value can contain the JSON itself or, if it ends with
@@ -257,6 +267,19 @@ public SELF isStrictlyEqualTo(Resource expected) {
257267
return isEqualTo(expected, JsonCompareMode.STRICT);
258268
}
259269

270+
/**
271+
* Verify that the actual value is {@linkplain JsonCompareMode#STRICT strictly}
272+
* not equal to the given JSON. The {@code expected} value can contain the
273+
* JSON itself or, if it ends with {@code .json}, the name of a resource to
274+
* be loaded from the classpath.
275+
* @param expected the expected JSON or the name of a resource containing
276+
* the expected JSON
277+
* @see #isNotEqualTo(CharSequence, JsonCompareMode)
278+
*/
279+
public SELF isNotEqualTo(@Nullable CharSequence expected) {
280+
return isNotEqualTo(expected, JsonCompareMode.STRICT);
281+
}
282+
260283
/**
261284
* Verify that the actual value is not equal to the given JSON. The
262285
* {@code expected} value can contain the JSON itself or, if it ends with
@@ -399,13 +422,24 @@ public SELF withCharset(@Nullable Charset charset) {
399422
return this.myself;
400423
}
401424

425+
@Nullable
426+
private String toJsonString() {
427+
return (this.actual != null ? this.actual.getJson() : null);
428+
}
429+
430+
@SuppressWarnings("NullAway")
431+
private String toNonNullJsonString() {
432+
String jsonString = toJsonString();
433+
Assertions.assertThat(jsonString).as("JSON content").isNotNull();
434+
return jsonString;
435+
}
402436

403437
private JsonComparison compare(@Nullable CharSequence expectedJson, JsonCompareMode compareMode) {
404438
return compare(expectedJson, JsonAssert.comparator(compareMode));
405439
}
406440

407441
private JsonComparison compare(@Nullable CharSequence expectedJson, JsonComparator comparator) {
408-
return comparator.compare((expectedJson != null) ? expectedJson.toString() : null, this.actual);
442+
return comparator.compare((expectedJson != null) ? expectedJson.toString() : null, toJsonString());
409443
}
410444

411445
private SELF assertIsMatch(JsonComparison result) {
@@ -435,16 +469,15 @@ private class JsonPathValue {
435469

436470
private final String path;
437471

438-
private final JsonPath jsonPath;
439-
440472
private final String json;
441473

474+
private final JsonPath jsonPath;
475+
442476
JsonPathValue(String path) {
443477
Assert.hasText(path, "'path' must not be null or empty");
444-
isNotNull();
445478
this.path = path;
479+
this.json = toNonNullJsonString();
446480
this.jsonPath = JsonPath.compile(this.path);
447-
this.json = AbstractJsonContentAssert.this.actual;
448481
}
449482

450483
@Nullable

spring-test/src/main/java/org/springframework/test/json/JsonContent.java

+24-7
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import org.assertj.core.api.AssertProvider;
2020

21+
import org.springframework.http.converter.GenericHttpMessageConverter;
2122
import org.springframework.lang.Nullable;
2223
import org.springframework.util.Assert;
2324

@@ -34,38 +35,54 @@ public final class JsonContent implements AssertProvider<JsonContentAssert> {
3435
private final String json;
3536

3637
@Nullable
37-
private final Class<?> resourceLoadClass;
38+
private final GenericHttpMessageConverter<Object> jsonMessageConverter;
3839

3940

4041
/**
41-
* Create a new {@code JsonContent} instance.
42+
* Create a new {@code JsonContent} instance with the message converter to
43+
* use to deserialize content.
4244
* @param json the actual JSON content
43-
* @param resourceLoadClass the source class used to load resources
45+
* @param jsonMessageConverter the message converter to use
4446
*/
45-
JsonContent(String json, @Nullable Class<?> resourceLoadClass) {
47+
public JsonContent(String json, @Nullable GenericHttpMessageConverter<Object> jsonMessageConverter) {
4648
Assert.notNull(json, "JSON must not be null");
4749
this.json = json;
48-
this.resourceLoadClass = resourceLoadClass;
50+
this.jsonMessageConverter = jsonMessageConverter;
4951
}
5052

5153

54+
/**
55+
* Create a new {@code JsonContent} instance.
56+
* @param json the actual JSON content
57+
*/
58+
public JsonContent(String json) {
59+
this(json, null);
60+
}
61+
5262
/**
5363
* Use AssertJ's {@link org.assertj.core.api.Assertions#assertThat assertThat}
5464
* instead.
5565
*/
5666
@Override
5767
public JsonContentAssert assertThat() {
58-
return new JsonContentAssert(this.json, null).withResourceLoadClass(this.resourceLoadClass);
68+
return new JsonContentAssert(this);
5969
}
6070

6171
/**
6272
* Return the actual JSON content string.
63-
* @return the JSON content
6473
*/
6574
public String getJson() {
6675
return this.json;
6776
}
6877

78+
/**
79+
* Return the message converter to use to deserialize content.
80+
*/
81+
@Nullable
82+
GenericHttpMessageConverter<Object> getJsonMessageConverter() {
83+
return this.jsonMessageConverter;
84+
}
85+
6986
@Override
7087
public String toString() {
7188
return "JsonContent " + this.json;

spring-test/src/main/java/org/springframework/test/json/JsonContentAssert.java

+2-6
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
package org.springframework.test.json;
1818

19-
import org.springframework.http.converter.GenericHttpMessageConverter;
2019
import org.springframework.lang.Nullable;
2120

2221
/**
@@ -29,13 +28,10 @@ public class JsonContentAssert extends AbstractJsonContentAssert<JsonContentAsse
2928

3029
/**
3130
* Create an assert for the given JSON document.
32-
* <p>Path can be converted to a value object using the given
33-
* {@linkplain GenericHttpMessageConverter JSON message converter}.
3431
* @param json the JSON document to assert
35-
* @param jsonMessageConverter the converter to use
3632
*/
37-
public JsonContentAssert(@Nullable String json, @Nullable GenericHttpMessageConverter<Object> jsonMessageConverter) {
38-
super(json, jsonMessageConverter, JsonContentAssert.class);
33+
public JsonContentAssert(@Nullable JsonContent json) {
34+
super(json, JsonContentAssert.class);
3935
}
4036

4137
}

spring-test/src/main/java/org/springframework/test/web/servlet/assertj/AbstractMockHttpServletResponseAssert.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.springframework.lang.Nullable;
2828
import org.springframework.mock.web.MockHttpServletResponse;
2929
import org.springframework.test.json.AbstractJsonContentAssert;
30+
import org.springframework.test.json.JsonContent;
3031
import org.springframework.test.json.JsonContentAssert;
3132
import org.springframework.test.web.UriAssert;
3233

@@ -92,7 +93,7 @@ public AbstractStringAssert<?> bodyText() {
9293
* </code></pre>
9394
*/
9495
public AbstractJsonContentAssert<?> bodyJson() {
95-
return new JsonContentAssert(readBody(), this.jsonMessageConverter);
96+
return new JsonContentAssert(new JsonContent(readBody(), this.jsonMessageConverter));
9697
}
9798

9899
private String readBody() {

spring-test/src/test/java/org/springframework/test/json/AbstractJsonContentAssertTests.java

+9-1
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,14 @@ void isNullWhenActualIsNullShouldPass() {
9292
assertThat(forJson(null)).isNull();
9393
}
9494

95+
@Test
96+
void satisfiesAllowFurtherAssertions() {
97+
assertThat(forJson(SIMPSONS)).satisfies(content -> {
98+
assertThat(content).extractingPath("$.familyMembers[0].name").isEqualTo("Homer");
99+
assertThat(content).extractingPath("$.familyMembers[1].name").isEqualTo("Marge");
100+
});
101+
}
102+
95103
@Nested
96104
class HasPathTests {
97105

@@ -831,7 +839,7 @@ private AssertProvider<AbstractJsonContentAssert<?>> forJson(@Nullable String js
831839
private static class TestJsonContentAssert extends AbstractJsonContentAssert<TestJsonContentAssert> {
832840

833841
public TestJsonContentAssert(@Nullable String json, @Nullable GenericHttpMessageConverter<Object> jsonMessageConverter) {
834-
super(json, jsonMessageConverter, TestJsonContentAssert.class);
842+
super((json != null ? new JsonContent(json, jsonMessageConverter) : null), TestJsonContentAssert.class);
835843
}
836844
}
837845

spring-test/src/test/java/org/springframework/test/json/JsonContentTests.java

+15-5
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,17 @@
1818

1919
import org.junit.jupiter.api.Test;
2020

21+
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
22+
2123
import static org.assertj.core.api.Assertions.assertThat;
2224
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
25+
import static org.mockito.Mockito.mock;
2326

2427
/**
2528
* Tests for {@link JsonContent}.
2629
*
2730
* @author Phillip Webb
31+
* @author Stephane Nicoll
2832
*/
2933
class JsonContentTests {
3034

@@ -34,27 +38,33 @@ class JsonContentTests {
3438
void createWhenJsonIsNullShouldThrowException() {
3539
assertThatIllegalArgumentException()
3640
.isThrownBy(
37-
() -> new JsonContent(null, null))
41+
() -> new JsonContent(null))
3842
.withMessageContaining("JSON must not be null");
3943
}
4044

4145
@Test
42-
@SuppressWarnings("deprecation")
4346
void assertThatShouldReturnJsonContentAssert() {
44-
JsonContent content = new JsonContent(JSON, getClass());
47+
JsonContent content = new JsonContent(JSON);
4548
assertThat(content.assertThat()).isInstanceOf(JsonContentAssert.class);
4649
}
4750

4851
@Test
4952
void getJsonShouldReturnJson() {
50-
JsonContent content = new JsonContent(JSON, getClass());
53+
JsonContent content = new JsonContent(JSON);
5154
assertThat(content.getJson()).isEqualTo(JSON);
5255
}
5356

5457
@Test
5558
void toStringShouldReturnString() {
56-
JsonContent content = new JsonContent(JSON, getClass());
59+
JsonContent content = new JsonContent(JSON);
5760
assertThat(content.toString()).isEqualTo("JsonContent " + JSON);
5861
}
5962

63+
@Test
64+
void getJsonMessageConverterShouldReturnConverter() {
65+
MappingJackson2HttpMessageConverter converter = mock(MappingJackson2HttpMessageConverter.class);
66+
JsonContent content = new JsonContent(JSON, converter);
67+
assertThat(content.getJsonMessageConverter()).isSameAs(converter);
68+
}
69+
6070
}

0 commit comments

Comments
 (0)