Skip to content
This repository was archived by the owner on Dec 19, 2023. It is now read-only.

Commit d197f0d

Browse files
feat: improvements for GraphQL response testing facilities
GraphQLResponse and GraphQLTestTemplate now use the ObjectMapper bean from the tested application. The `get` methods now use the object mapper to convert the variable to the specified type. Formerly they used vanilla JsonPath library methods that may produce unexpected results. E. g. given the following response body: `{"data": {"test": 2}}` a call to `get("$.data.test")` would result in an `ClassCastException`, which is usually not the desired behaviour. It was also very restrictive on data types: only "native" JSON types were supported (e. g. int, boolean, String). Furthermore it seems to have issues with more complex classes, like POJOs and may return `null`. Several test cases were added to ensure correct behaviour. Some of these tests would fail with the previous version.
1 parent 532e04b commit d197f0d

File tree

5 files changed

+151
-11
lines changed

5 files changed

+151
-11
lines changed

graphql-spring-boot-test/build.gradle

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,16 @@
1919
dependencies {
2020
compile("org.springframework:spring-web:$LIB_SPRING_CORE_VER")
2121
compile("org.springframework.boot:spring-boot-starter-test:$LIB_SPRING_BOOT_VER")
22-
compile("com.fasterxml.jackson.core:jackson-databind:2.5.4")
23-
compile("com.jayway.jsonpath:json-path:2.3.0")
22+
compile("com.fasterxml.jackson.core:jackson-databind:2.10.2")
23+
compile("com.jayway.jsonpath:json-path:2.4.0")
2424
compileOnly("com.graphql-java:graphql-java:$LIB_GRAPHQL_JAVA_VER")
2525
compileOnly("com.graphql-java-kickstart:graphql-java-servlet:$LIB_GRAPHQL_SERVLET_VER")
26+
testImplementation("org.springframework.boot:spring-boot-starter-web:$LIB_SPRING_BOOT_VER")
2627
}
2728
repositories {
2829
mavenCentral()
2930
}
31+
32+
test {
33+
useJUnitPlatform()
34+
}

graphql-spring-boot-test/src/main/java/com/graphql/spring/boot/test/GraphQLResponse.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@
1313

1414
public class GraphQLResponse {
1515

16-
private ResponseEntity<String> responseEntity;
17-
private ObjectMapper mapper;
18-
private ReadContext context;
16+
private final ResponseEntity<String> responseEntity;
17+
private final ObjectMapper mapper;
18+
private final ReadContext context;
1919

20-
public GraphQLResponse(ResponseEntity<String> responseEntity) {
20+
public GraphQLResponse(ResponseEntity<String> responseEntity, ObjectMapper objectMapper) {
2121
this.responseEntity = Objects.requireNonNull(responseEntity);
22-
this.mapper = new ObjectMapper();
22+
this.mapper = Objects.requireNonNull(objectMapper);
2323

2424
Objects.requireNonNull(responseEntity.getBody(),
2525
() -> "Body is empty with status " + responseEntity.getStatusCodeValue());
@@ -31,11 +31,11 @@ public JsonNode readTree() throws IOException {
3131
}
3232

3333
public String get(String path) {
34-
return context.read(path);
34+
return get(path, String.class);
3535
}
3636

3737
public <T> T get(String path, Class<T> type) {
38-
return context.read(path, type);
38+
return mapper.convertValue(context.read(path, Object.class), type);
3939
}
4040

4141
public <T> List<T> getList(String path, Class<T> type) {

graphql-spring-boot-test/src/main/java/com/graphql/spring/boot/test/GraphQLTestTemplate.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,9 @@ public class GraphQLTestTemplate {
2727
private TestRestTemplate restTemplate;
2828
@Value("${graphql.servlet.mapping:/graphql}")
2929
private String graphqlMapping;
30+
@Autowired
31+
private ObjectMapper objectMapper;
3032

31-
private ObjectMapper objectMapper = new ObjectMapper();
3233
private HttpHeaders headers = new HttpHeaders();
3334

3435
private String createJsonQuery(String graphql, ObjectNode variables)
@@ -138,7 +139,7 @@ private GraphQLResponse post(String payload) {
138139

139140
private GraphQLResponse postRequest(HttpEntity<Object> request) {
140141
ResponseEntity<String> response = restTemplate.exchange(graphqlMapping, HttpMethod.POST, request, String.class);
141-
return new GraphQLResponse(response);
142+
return new GraphQLResponse(response, objectMapper);
142143
}
143144

144145
}
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
package com.graphql.spring.boot.test;
2+
3+
import com.fasterxml.jackson.databind.ObjectMapper;
4+
import lombok.AllArgsConstructor;
5+
import lombok.Data;
6+
import lombok.NoArgsConstructor;
7+
import org.junit.jupiter.api.DisplayName;
8+
import org.junit.jupiter.params.ParameterizedTest;
9+
import org.junit.jupiter.params.provider.Arguments;
10+
import org.junit.jupiter.params.provider.MethodSource;
11+
import org.springframework.beans.factory.annotation.Autowired;
12+
import org.springframework.boot.test.context.SpringBootTest;
13+
import org.springframework.http.ResponseEntity;
14+
15+
import java.math.BigDecimal;
16+
import java.time.LocalDate;
17+
import java.util.Arrays;
18+
import java.util.List;
19+
import java.util.stream.Stream;
20+
21+
import static org.assertj.core.api.Assertions.assertThat;
22+
23+
@SpringBootTest(classes = TestApplication.class)
24+
public class GraphQLResponseTest {
25+
26+
private static final String DATA_PATH = "$.data.test";
27+
28+
@Autowired
29+
private ObjectMapper objectMapper;
30+
31+
private static Stream<Arguments> testGetStringArguments() {
32+
return Stream.of(
33+
Arguments.of("{\"data\": {\"test\": 2}}", "2"),
34+
Arguments.of("{\"data\": {\"test\": \"2\"}}", "2"),
35+
Arguments.of("{\"data\": {\"test\": \"2020-02-23\"}}", "2020-02-23")
36+
);
37+
}
38+
39+
private static Stream<Arguments> testGetArguments() {
40+
return Stream.of(
41+
Arguments.of("{\"data\": {\"test\": \"2\"}}", Integer.class, 2),
42+
Arguments.of("{\"data\": {\"test\": \"2\"}}", String.class, "2"),
43+
Arguments.of("{\"data\": {\"test\": \"2\"}}", BigDecimal.class, new BigDecimal("2")),
44+
Arguments.of("{\"data\": {\"test\": \"2020-02-23\"}}", LocalDate.class, LocalDate.parse("2020-02-23")),
45+
Arguments.of("{\"data\": {\"test\": {\"foo\": \"fizzBuzz\", \"bar\": 13.8 }}}", FooBar.class,
46+
new FooBar("fizzBuzz", new BigDecimal("13.8")))
47+
);
48+
}
49+
50+
private static Stream<Arguments> testGetListArguments() {
51+
return Stream.of(
52+
Arguments.of("{\"data\": {\"test\": [\"2\", \"1\"]}}", Integer.class, Arrays.asList(2, 1)),
53+
Arguments.of("{\"data\": {\"test\": [\"2\", \"1\"]}}", String.class, Arrays.asList("2", "1")),
54+
Arguments.of("{\"data\": {\"test\": [\"2\", \"1\"]}}", BigDecimal.class,
55+
Arrays.asList(new BigDecimal("2"), new BigDecimal("1"))),
56+
Arguments.of("{\"data\": {\"test\": [\"2020-02-23\", \"2020-02-24\"]}}", LocalDate.class,
57+
Arrays.asList(LocalDate.parse("2020-02-23"), LocalDate.parse("2020-02-24"))),
58+
Arguments.of("{\"data\":{\"test\":[{\"foo\":\"fizz\",\"bar\":1.23},{\"foo\":\"buzz\",\"bar\":32.12}]}}",
59+
FooBar.class,
60+
Arrays.asList(
61+
new FooBar("fizz", new BigDecimal("1.23")),
62+
new FooBar("buzz", new BigDecimal("32.12"))
63+
)
64+
)
65+
);
66+
}
67+
68+
@DisplayName("Should get the JSON node's value as a String.")
69+
@ParameterizedTest
70+
@MethodSource("testGetStringArguments")
71+
public void testGetString(
72+
final String bodyString,
73+
final String expected
74+
) {
75+
//GIVEN
76+
final GraphQLResponse graphQLResponse = new GraphQLResponse(ResponseEntity.ok(bodyString), objectMapper);
77+
//WHEN
78+
final String actual = graphQLResponse.get(DATA_PATH);
79+
//THEN
80+
assertThat(actual).isEqualTo(expected);
81+
}
82+
83+
@DisplayName("Should get the JSON node's value as an instance of a specified class.")
84+
@ParameterizedTest
85+
@MethodSource("testGetArguments")
86+
public <T> void testGet(
87+
final String bodyString,
88+
final Class<T> clazz,
89+
final T expected
90+
) {
91+
//GIVEN
92+
final GraphQLResponse graphQLResponse = new GraphQLResponse(ResponseEntity.ok(bodyString), objectMapper);
93+
//WHEN
94+
final T actual = graphQLResponse.get(DATA_PATH, clazz);
95+
//THEN
96+
assertThat(actual).isInstanceOf(clazz).isEqualTo(expected);
97+
}
98+
99+
@DisplayName("Should get the JSON node's value as a List.")
100+
@ParameterizedTest
101+
@MethodSource("testGetListArguments")
102+
public <T> void testGetList(
103+
final String bodyString,
104+
final Class<T> clazz,
105+
final List<T> expected
106+
) {
107+
//GIVEN
108+
final GraphQLResponse graphQLResponse = new GraphQLResponse(ResponseEntity.ok(bodyString), objectMapper);
109+
//WHEN
110+
final List<T> actual = graphQLResponse.getList(DATA_PATH, clazz);
111+
//THEN
112+
assertThat(actual).containsExactlyElementsOf(expected);
113+
}
114+
115+
@Data
116+
@AllArgsConstructor
117+
@NoArgsConstructor
118+
private static class FooBar {
119+
private String foo;
120+
private BigDecimal bar;
121+
}
122+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.graphql.spring.boot.test;
2+
3+
import org.springframework.boot.SpringApplication;
4+
import org.springframework.boot.autoconfigure.SpringBootApplication;
5+
6+
@SpringBootApplication
7+
public class TestApplication {
8+
9+
public static void main(String[] args) {
10+
SpringApplication.run(TestApplication.class, args);
11+
}
12+
}

0 commit comments

Comments
 (0)