Skip to content

Commit aacd00c

Browse files
committed
add ResponseBodyMatchers used in EMVA and AICHNER
1 parent 79ac923 commit aacd00c

1 file changed

Lines changed: 95 additions & 0 deletions

File tree

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

0 commit comments

Comments
 (0)