From d197f0dbec164e6832156c41b240656e3796ff24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Vajner?= Date: Sun, 23 Feb 2020 22:46:14 +0100 Subject: [PATCH] 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. --- graphql-spring-boot-test/build.gradle | 9 +- .../spring/boot/test/GraphQLResponse.java | 14 +- .../spring/boot/test/GraphQLTestTemplate.java | 5 +- .../spring/boot/test/GraphQLResponseTest.java | 122 ++++++++++++++++++ .../spring/boot/test/TestApplication.java | 12 ++ 5 files changed, 151 insertions(+), 11 deletions(-) create mode 100644 graphql-spring-boot-test/src/test/java/com/graphql/spring/boot/test/GraphQLResponseTest.java create mode 100644 graphql-spring-boot-test/src/test/java/com/graphql/spring/boot/test/TestApplication.java diff --git a/graphql-spring-boot-test/build.gradle b/graphql-spring-boot-test/build.gradle index 240e2690..15b2899f 100644 --- a/graphql-spring-boot-test/build.gradle +++ b/graphql-spring-boot-test/build.gradle @@ -19,11 +19,16 @@ dependencies { compile("org.springframework:spring-web:$LIB_SPRING_CORE_VER") compile("org.springframework.boot:spring-boot-starter-test:$LIB_SPRING_BOOT_VER") - compile("com.fasterxml.jackson.core:jackson-databind:2.5.4") - compile("com.jayway.jsonpath:json-path:2.3.0") + compile("com.fasterxml.jackson.core:jackson-databind:2.10.2") + compile("com.jayway.jsonpath:json-path:2.4.0") compileOnly("com.graphql-java:graphql-java:$LIB_GRAPHQL_JAVA_VER") compileOnly("com.graphql-java-kickstart:graphql-java-servlet:$LIB_GRAPHQL_SERVLET_VER") + testImplementation("org.springframework.boot:spring-boot-starter-web:$LIB_SPRING_BOOT_VER") } repositories { mavenCentral() } + +test { + useJUnitPlatform() +} diff --git a/graphql-spring-boot-test/src/main/java/com/graphql/spring/boot/test/GraphQLResponse.java b/graphql-spring-boot-test/src/main/java/com/graphql/spring/boot/test/GraphQLResponse.java index eb03b141..1ae82fb8 100644 --- a/graphql-spring-boot-test/src/main/java/com/graphql/spring/boot/test/GraphQLResponse.java +++ b/graphql-spring-boot-test/src/main/java/com/graphql/spring/boot/test/GraphQLResponse.java @@ -13,13 +13,13 @@ public class GraphQLResponse { - private ResponseEntity responseEntity; - private ObjectMapper mapper; - private ReadContext context; + private final ResponseEntity responseEntity; + private final ObjectMapper mapper; + private final ReadContext context; - public GraphQLResponse(ResponseEntity responseEntity) { + public GraphQLResponse(ResponseEntity responseEntity, ObjectMapper objectMapper) { this.responseEntity = Objects.requireNonNull(responseEntity); - this.mapper = new ObjectMapper(); + this.mapper = Objects.requireNonNull(objectMapper); Objects.requireNonNull(responseEntity.getBody(), () -> "Body is empty with status " + responseEntity.getStatusCodeValue()); @@ -31,11 +31,11 @@ public JsonNode readTree() throws IOException { } public String get(String path) { - return context.read(path); + return get(path, String.class); } public T get(String path, Class type) { - return context.read(path, type); + return mapper.convertValue(context.read(path, Object.class), type); } public List getList(String path, Class type) { diff --git a/graphql-spring-boot-test/src/main/java/com/graphql/spring/boot/test/GraphQLTestTemplate.java b/graphql-spring-boot-test/src/main/java/com/graphql/spring/boot/test/GraphQLTestTemplate.java index 69b0058a..3b618c30 100644 --- a/graphql-spring-boot-test/src/main/java/com/graphql/spring/boot/test/GraphQLTestTemplate.java +++ b/graphql-spring-boot-test/src/main/java/com/graphql/spring/boot/test/GraphQLTestTemplate.java @@ -27,8 +27,9 @@ public class GraphQLTestTemplate { private TestRestTemplate restTemplate; @Value("${graphql.servlet.mapping:/graphql}") private String graphqlMapping; + @Autowired + private ObjectMapper objectMapper; - private ObjectMapper objectMapper = new ObjectMapper(); private HttpHeaders headers = new HttpHeaders(); private String createJsonQuery(String graphql, ObjectNode variables) @@ -138,7 +139,7 @@ private GraphQLResponse post(String payload) { private GraphQLResponse postRequest(HttpEntity request) { ResponseEntity response = restTemplate.exchange(graphqlMapping, HttpMethod.POST, request, String.class); - return new GraphQLResponse(response); + return new GraphQLResponse(response, objectMapper); } } \ No newline at end of file diff --git a/graphql-spring-boot-test/src/test/java/com/graphql/spring/boot/test/GraphQLResponseTest.java b/graphql-spring-boot-test/src/test/java/com/graphql/spring/boot/test/GraphQLResponseTest.java new file mode 100644 index 00000000..ccb4628e --- /dev/null +++ b/graphql-spring-boot-test/src/test/java/com/graphql/spring/boot/test/GraphQLResponseTest.java @@ -0,0 +1,122 @@ +package com.graphql.spring.boot.test; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.ResponseEntity; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest(classes = TestApplication.class) +public class GraphQLResponseTest { + + private static final String DATA_PATH = "$.data.test"; + + @Autowired + private ObjectMapper objectMapper; + + private static Stream testGetStringArguments() { + return Stream.of( + Arguments.of("{\"data\": {\"test\": 2}}", "2"), + Arguments.of("{\"data\": {\"test\": \"2\"}}", "2"), + Arguments.of("{\"data\": {\"test\": \"2020-02-23\"}}", "2020-02-23") + ); + } + + private static Stream testGetArguments() { + return Stream.of( + Arguments.of("{\"data\": {\"test\": \"2\"}}", Integer.class, 2), + Arguments.of("{\"data\": {\"test\": \"2\"}}", String.class, "2"), + Arguments.of("{\"data\": {\"test\": \"2\"}}", BigDecimal.class, new BigDecimal("2")), + Arguments.of("{\"data\": {\"test\": \"2020-02-23\"}}", LocalDate.class, LocalDate.parse("2020-02-23")), + Arguments.of("{\"data\": {\"test\": {\"foo\": \"fizzBuzz\", \"bar\": 13.8 }}}", FooBar.class, + new FooBar("fizzBuzz", new BigDecimal("13.8"))) + ); + } + + private static Stream testGetListArguments() { + return Stream.of( + Arguments.of("{\"data\": {\"test\": [\"2\", \"1\"]}}", Integer.class, Arrays.asList(2, 1)), + Arguments.of("{\"data\": {\"test\": [\"2\", \"1\"]}}", String.class, Arrays.asList("2", "1")), + Arguments.of("{\"data\": {\"test\": [\"2\", \"1\"]}}", BigDecimal.class, + Arrays.asList(new BigDecimal("2"), new BigDecimal("1"))), + Arguments.of("{\"data\": {\"test\": [\"2020-02-23\", \"2020-02-24\"]}}", LocalDate.class, + Arrays.asList(LocalDate.parse("2020-02-23"), LocalDate.parse("2020-02-24"))), + Arguments.of("{\"data\":{\"test\":[{\"foo\":\"fizz\",\"bar\":1.23},{\"foo\":\"buzz\",\"bar\":32.12}]}}", + FooBar.class, + Arrays.asList( + new FooBar("fizz", new BigDecimal("1.23")), + new FooBar("buzz", new BigDecimal("32.12")) + ) + ) + ); + } + + @DisplayName("Should get the JSON node's value as a String.") + @ParameterizedTest + @MethodSource("testGetStringArguments") + public void testGetString( + final String bodyString, + final String expected + ) { + //GIVEN + final GraphQLResponse graphQLResponse = new GraphQLResponse(ResponseEntity.ok(bodyString), objectMapper); + //WHEN + final String actual = graphQLResponse.get(DATA_PATH); + //THEN + assertThat(actual).isEqualTo(expected); + } + + @DisplayName("Should get the JSON node's value as an instance of a specified class.") + @ParameterizedTest + @MethodSource("testGetArguments") + public void testGet( + final String bodyString, + final Class clazz, + final T expected + ) { + //GIVEN + final GraphQLResponse graphQLResponse = new GraphQLResponse(ResponseEntity.ok(bodyString), objectMapper); + //WHEN + final T actual = graphQLResponse.get(DATA_PATH, clazz); + //THEN + assertThat(actual).isInstanceOf(clazz).isEqualTo(expected); + } + + @DisplayName("Should get the JSON node's value as a List.") + @ParameterizedTest + @MethodSource("testGetListArguments") + public void testGetList( + final String bodyString, + final Class clazz, + final List expected + ) { + //GIVEN + final GraphQLResponse graphQLResponse = new GraphQLResponse(ResponseEntity.ok(bodyString), objectMapper); + //WHEN + final List actual = graphQLResponse.getList(DATA_PATH, clazz); + //THEN + assertThat(actual).containsExactlyElementsOf(expected); + } + + @Data + @AllArgsConstructor + @NoArgsConstructor + private static class FooBar { + private String foo; + private BigDecimal bar; + } +} diff --git a/graphql-spring-boot-test/src/test/java/com/graphql/spring/boot/test/TestApplication.java b/graphql-spring-boot-test/src/test/java/com/graphql/spring/boot/test/TestApplication.java new file mode 100644 index 00000000..8f4ab79f --- /dev/null +++ b/graphql-spring-boot-test/src/test/java/com/graphql/spring/boot/test/TestApplication.java @@ -0,0 +1,12 @@ +package com.graphql.spring.boot.test; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class TestApplication { + + public static void main(String[] args) { + SpringApplication.run(TestApplication.class, args); + } +}