Skip to content

Commit

Permalink
Allow JSON content assertions to be nested
Browse files Browse the repository at this point in the history
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
  • Loading branch information
snicoll committed May 27, 2024
1 parent 489d18a commit e2c5887
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@

import com.jayway.jsonpath.JsonPath;
import com.jayway.jsonpath.PathNotFoundException;
import org.assertj.core.api.AbstractStringAssert;
import org.assertj.core.api.AbstractObjectAssert;
import org.assertj.core.api.AssertProvider;
import org.assertj.core.api.Assertions;
import org.assertj.core.error.BasicErrorMessageFactory;
import org.assertj.core.internal.Failures;

Expand Down Expand Up @@ -61,7 +62,7 @@
* @param <SELF> the type of assertions
*/
public abstract class AbstractJsonContentAssert<SELF extends AbstractJsonContentAssert<SELF>>
extends AbstractStringAssert<SELF> {
extends AbstractObjectAssert<SELF, JsonContent> {

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

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

/**
* Create an assert for the given JSON document.
* <p>Path can be converted to a value object using the given
* {@linkplain GenericHttpMessageConverter JSON message converter}.
* @param json the JSON document to assert
* @param jsonMessageConverter the converter to use
* @param actual the JSON document to assert
* @param selfType the implementation type of this assert
*/
protected AbstractJsonContentAssert(@Nullable String json,
@Nullable GenericHttpMessageConverter<Object> jsonMessageConverter, Class<?> selfType) {
super(json, selfType);
this.jsonMessageConverter = jsonMessageConverter;
protected AbstractJsonContentAssert(@Nullable JsonContent actual, Class<?> selfType) {
super(actual, selfType);
this.jsonMessageConverter = (actual != null ? actual.getJsonMessageConverter() : null);
this.jsonLoader = new JsonLoader(null, null);
as("JSON content");
}
Expand Down Expand Up @@ -141,6 +138,19 @@ public SELF doesNotHavePath(String path) {

// JsonAssert support

/**
* Verify that the actual value is {@linkplain 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.
* @param expected the expected JSON or the name of a resource containing
* the expected JSON
* @see #isEqualTo(CharSequence, JsonCompareMode)
*/
public SELF isEqualTo(@Nullable CharSequence expected) {
return isEqualTo(expected, JsonCompareMode.STRICT);
}

/**
* Verify that the actual value is equal to the given JSON. The
* {@code expected} value can contain the JSON itself or, if it ends with
Expand Down Expand Up @@ -257,6 +267,19 @@ public SELF isStrictlyEqualTo(Resource expected) {
return isEqualTo(expected, JsonCompareMode.STRICT);
}

/**
* Verify that the actual value is {@linkplain JsonCompareMode#STRICT strictly}
* not 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.
* @param expected the expected JSON or the name of a resource containing
* the expected JSON
* @see #isNotEqualTo(CharSequence, JsonCompareMode)
*/
public SELF isNotEqualTo(@Nullable CharSequence expected) {
return isNotEqualTo(expected, JsonCompareMode.STRICT);
}

/**
* Verify that the actual value is not equal to the given JSON. The
* {@code expected} value can contain the JSON itself or, if it ends with
Expand Down Expand Up @@ -399,13 +422,24 @@ public SELF withCharset(@Nullable Charset charset) {
return this.myself;
}

@Nullable
private String toJsonString() {
return (this.actual != null ? this.actual.getJson() : null);
}

@SuppressWarnings("NullAway")
private String toNonNullJsonString() {
String jsonString = toJsonString();
Assertions.assertThat(jsonString).as("JSON content").isNotNull();
return jsonString;
}

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

private JsonComparison compare(@Nullable CharSequence expectedJson, JsonComparator comparator) {
return comparator.compare((expectedJson != null) ? expectedJson.toString() : null, this.actual);
return comparator.compare((expectedJson != null) ? expectedJson.toString() : null, toJsonString());
}

private SELF assertIsMatch(JsonComparison result) {
Expand Down Expand Up @@ -435,16 +469,15 @@ private class JsonPathValue {

private final String path;

private final JsonPath jsonPath;

private final String json;

private final JsonPath jsonPath;

JsonPathValue(String path) {
Assert.hasText(path, "'path' must not be null or empty");
isNotNull();
this.path = path;
this.json = toNonNullJsonString();
this.jsonPath = JsonPath.compile(this.path);
this.json = AbstractJsonContentAssert.this.actual;
}

@Nullable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import org.assertj.core.api.AssertProvider;

import org.springframework.http.converter.GenericHttpMessageConverter;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;

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

@Nullable
private final Class<?> resourceLoadClass;
private final GenericHttpMessageConverter<Object> jsonMessageConverter;


/**
* Create a new {@code JsonContent} instance.
* Create a new {@code JsonContent} instance with the message converter to
* use to deserialize content.
* @param json the actual JSON content
* @param resourceLoadClass the source class used to load resources
* @param jsonMessageConverter the message converter to use
*/
JsonContent(String json, @Nullable Class<?> resourceLoadClass) {
public JsonContent(String json, @Nullable GenericHttpMessageConverter<Object> jsonMessageConverter) {
Assert.notNull(json, "JSON must not be null");
this.json = json;
this.resourceLoadClass = resourceLoadClass;
this.jsonMessageConverter = jsonMessageConverter;
}


/**
* Create a new {@code JsonContent} instance.
* @param json the actual JSON content
*/
public JsonContent(String json) {
this(json, null);
}

/**
* Use AssertJ's {@link org.assertj.core.api.Assertions#assertThat assertThat}
* instead.
*/
@Override
public JsonContentAssert assertThat() {
return new JsonContentAssert(this.json, null).withResourceLoadClass(this.resourceLoadClass);
return new JsonContentAssert(this);
}

/**
* Return the actual JSON content string.
* @return the JSON content
*/
public String getJson() {
return this.json;
}

/**
* Return the message converter to use to deserialize content.
*/
@Nullable
GenericHttpMessageConverter<Object> getJsonMessageConverter() {
return this.jsonMessageConverter;
}

@Override
public String toString() {
return "JsonContent " + this.json;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

package org.springframework.test.json;

import org.springframework.http.converter.GenericHttpMessageConverter;
import org.springframework.lang.Nullable;

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

/**
* Create an assert for the given JSON document.
* <p>Path can be converted to a value object using the given
* {@linkplain GenericHttpMessageConverter JSON message converter}.
* @param json the JSON document to assert
* @param jsonMessageConverter the converter to use
*/
public JsonContentAssert(@Nullable String json, @Nullable GenericHttpMessageConverter<Object> jsonMessageConverter) {
super(json, jsonMessageConverter, JsonContentAssert.class);
public JsonContentAssert(@Nullable JsonContent json) {
super(json, JsonContentAssert.class);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import org.springframework.lang.Nullable;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.test.json.AbstractJsonContentAssert;
import org.springframework.test.json.JsonContent;
import org.springframework.test.json.JsonContentAssert;
import org.springframework.test.web.UriAssert;

Expand Down Expand Up @@ -92,7 +93,7 @@ public AbstractStringAssert<?> bodyText() {
* </code></pre>
*/
public AbstractJsonContentAssert<?> bodyJson() {
return new JsonContentAssert(readBody(), this.jsonMessageConverter);
return new JsonContentAssert(new JsonContent(readBody(), this.jsonMessageConverter));
}

private String readBody() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,14 @@ void isNullWhenActualIsNullShouldPass() {
assertThat(forJson(null)).isNull();
}

@Test
void satisfiesAllowFurtherAssertions() {
assertThat(forJson(SIMPSONS)).satisfies(content -> {
assertThat(content).extractingPath("$.familyMembers[0].name").isEqualTo("Homer");
assertThat(content).extractingPath("$.familyMembers[1].name").isEqualTo("Marge");
});
}

@Nested
class HasPathTests {

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

public TestJsonContentAssert(@Nullable String json, @Nullable GenericHttpMessageConverter<Object> jsonMessageConverter) {
super(json, jsonMessageConverter, TestJsonContentAssert.class);
super((json != null ? new JsonContent(json, jsonMessageConverter) : null), TestJsonContentAssert.class);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,17 @@

import org.junit.jupiter.api.Test;

import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.Mockito.mock;

/**
* Tests for {@link JsonContent}.
*
* @author Phillip Webb
* @author Stephane Nicoll
*/
class JsonContentTests {

Expand All @@ -34,27 +38,33 @@ class JsonContentTests {
void createWhenJsonIsNullShouldThrowException() {
assertThatIllegalArgumentException()
.isThrownBy(
() -> new JsonContent(null, null))
() -> new JsonContent(null))
.withMessageContaining("JSON must not be null");
}

@Test
@SuppressWarnings("deprecation")
void assertThatShouldReturnJsonContentAssert() {
JsonContent content = new JsonContent(JSON, getClass());
JsonContent content = new JsonContent(JSON);
assertThat(content.assertThat()).isInstanceOf(JsonContentAssert.class);
}

@Test
void getJsonShouldReturnJson() {
JsonContent content = new JsonContent(JSON, getClass());
JsonContent content = new JsonContent(JSON);
assertThat(content.getJson()).isEqualTo(JSON);
}

@Test
void toStringShouldReturnString() {
JsonContent content = new JsonContent(JSON, getClass());
JsonContent content = new JsonContent(JSON);
assertThat(content.toString()).isEqualTo("JsonContent " + JSON);
}

@Test
void getJsonMessageConverterShouldReturnConverter() {
MappingJackson2HttpMessageConverter converter = mock(MappingJackson2HttpMessageConverter.class);
JsonContent content = new JsonContent(JSON, converter);
assertThat(content.getJsonMessageConverter()).isSameAs(converter);
}

}

0 comments on commit e2c5887

Please sign in to comment.