Skip to content

Commit b6a9b5a

Browse files
authored
Add response body matchers (#37)
* add ResponseBodyMatchers used in EMVA and AICHNER * make json mapper private * fix reviewed issues * fix style violations * fix reviewed issues * fix reviewed issues
1 parent 79ac923 commit b6a9b5a

1 file changed

Lines changed: 98 additions & 0 deletions

File tree

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package it.aboutbits.springboot.testing.web.response;
2+
3+
import com.jayway.jsonpath.JsonPath;
4+
import org.jspecify.annotations.NullMarked;
5+
import org.springframework.test.web.servlet.ResultMatcher;
6+
import tools.jackson.databind.json.JsonMapper;
7+
8+
import java.math.BigDecimal;
9+
import java.time.LocalDateTime;
10+
import java.time.OffsetDateTime;
11+
import java.time.temporal.ChronoUnit;
12+
import java.util.ArrayList;
13+
import java.util.Arrays;
14+
15+
import static org.assertj.core.api.Assertions.assertThat;
16+
import static org.assertj.core.api.Assertions.assertThatCode;
17+
import static org.assertj.core.api.Assertions.fail;
18+
19+
@NullMarked
20+
public final class ResponseBodyMatchers {
21+
private static final JsonMapper JSON_MAPPER = new JsonMapper();
22+
private final String jsonPath;
23+
24+
private String[] fieldNamesToIgnore = new String[0];
25+
private String[] optionalFieldNamesToIgnore = new String[0];
26+
private boolean ignoreAuditFields = false;
27+
28+
private ResponseBodyMatchers(String jsonPath) {
29+
this.jsonPath = jsonPath;
30+
}
31+
32+
public static ResponseBodyMatchers responseBody() {
33+
return new ResponseBodyMatchers("$");
34+
}
35+
36+
public static ResponseBodyMatchers responseBodyAt(String expression) {
37+
return new ResponseBodyMatchers(expression);
38+
}
39+
40+
public ResponseBodyMatchers ignoringOptionalFields(String... optionalFieldNamesToIgnore) {
41+
this.optionalFieldNamesToIgnore = optionalFieldNamesToIgnore;
42+
return this;
43+
}
44+
45+
public ResponseBodyMatchers ignoringFields(String... fieldNamesToIgnore) {
46+
this.fieldNamesToIgnore = fieldNamesToIgnore;
47+
return this;
48+
}
49+
50+
public ResponseBodyMatchers ignoringAuditFields() {
51+
this.ignoreAuditFields = true;
52+
return this;
53+
}
54+
55+
public <T> ResultMatcher containsObjectAsJson(
56+
Object expectedObject,
57+
Class<T> targetClass
58+
) {
59+
return mvcResult -> {
60+
try {
61+
var json = mvcResult.getResponse().getContentAsString();
62+
var extractedValue = JsonPath.read(json, jsonPath);
63+
T actualObject = JSON_MAPPER.convertValue(extractedValue, targetClass);
64+
65+
var ignoredFields = new ArrayList<>(Arrays.asList(fieldNamesToIgnore));
66+
ignoredFields.addAll(Arrays.asList(optionalFieldNamesToIgnore));
67+
if (ignoreAuditFields) {
68+
ignoredFields.addAll(Arrays.asList("createdAt", "createdBy", "updatedAt", "updatedBy"));
69+
}
70+
71+
assertThat(actualObject)
72+
.usingComparatorForType(BigDecimal::compareTo, BigDecimal.class)
73+
.usingComparatorForType(ResponseBodyMatchers::compareTemporalByDelta, OffsetDateTime.class)
74+
.usingComparatorForType(ResponseBodyMatchers::compareTemporalByDelta, LocalDateTime.class)
75+
.usingRecursiveComparison()
76+
.ignoringFields(ignoredFields.toArray(new String[0]))
77+
.isEqualTo(expectedObject);
78+
79+
for (var requiredFieldName : fieldNamesToIgnore) {
80+
assertThatCode(
81+
() -> JsonPath.read(json, jsonPath + "." + requiredFieldName)
82+
).doesNotThrowAnyException();
83+
}
84+
} catch (Exception e) {
85+
fail("Unable to extract result set. Maybe the path does not contain an object of that type?", e);
86+
}
87+
};
88+
}
89+
90+
// Not every machine has 9 digits precision, so we need to compare with a delta
91+
private static int compareTemporalByDelta(OffsetDateTime a, OffsetDateTime b) {
92+
return Math.abs(ChronoUnit.NANOS.between(a, b)) < 1000 ? 0 : a.compareTo(b);
93+
}
94+
95+
private static int compareTemporalByDelta(LocalDateTime a, LocalDateTime b) {
96+
return Math.abs(ChronoUnit.NANOS.between(a, b)) < 1000 ? 0 : a.compareTo(b);
97+
}
98+
}

0 commit comments

Comments
 (0)