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

Commit 1c7aa93

Browse files
authored
Merge pull request #443 from BlasiusSecundus/feature/graphql-test-template-upgrade
feat: add fluent API support for test template and response
2 parents 13cd484 + b756b45 commit 1c7aa93

File tree

54 files changed

+2873
-89
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+2873
-89
lines changed

graphql-spring-boot-test-autoconfigure/src/main/java/com/graphql/spring/boot/test/GraphQLTestAutoConfiguration.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@
22

33
import com.fasterxml.jackson.databind.ObjectMapper;
44
import org.springframework.beans.factory.annotation.Value;
5+
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
56
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
67
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
78
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
9+
import org.springframework.boot.test.web.client.TestRestTemplate;
810
import org.springframework.context.annotation.Bean;
911
import org.springframework.context.annotation.Configuration;
1012
import org.springframework.core.env.Environment;
13+
import org.springframework.core.io.ResourceLoader;
1114

1215
@Configuration
1316
@ConditionalOnWebApplication
@@ -16,8 +19,14 @@ public class GraphQLTestAutoConfiguration {
1619

1720
@Bean
1821
@ConditionalOnMissingBean
19-
public GraphQLTestTemplate graphQLTestUtils() {
20-
return new GraphQLTestTemplate();
22+
public GraphQLTestTemplate graphQLTestUtils(
23+
final ResourceLoader resourceLoader,
24+
final TestRestTemplate restTemplate,
25+
@Value("${graphql.servlet.mapping:/graphql}")
26+
final String graphqlMapping,
27+
final ObjectMapper objectMapper
28+
) {
29+
return new GraphQLTestTemplate(resourceLoader, restTemplate, graphqlMapping, objectMapper);
2130
}
2231

2332
@Bean

graphql-spring-boot-test-autoconfigure/src/test/java/com/graphql/spring/boot/test/GraphQLTestAutoConfigurationTestBase.java

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,23 +18,20 @@ public class GraphQLTestAutoConfigurationTestBase {
1818

1919
void assertThatTestSubscriptionWorksCorrectly() {
2020
// GIVEN
21-
final GraphQLTestSubscription graphQLTestSubscription
22-
= applicationContext.getBean(GraphQLTestSubscription.class);
23-
// WHEN
24-
final GraphQLResponse graphQLResponse
25-
= graphQLTestSubscription.start("test-subscription.graphql").awaitAndGetNextResponse(1000);
26-
// THEN
27-
assertThat(graphQLResponse.get("$.data.testSubscription")).isEqualTo(FOO);
21+
final GraphQLTestSubscription testSubscription = applicationContext.getBean(GraphQLTestSubscription.class);
22+
// WHEN - THEN
23+
testSubscription.start("test-subscription.graphql")
24+
.awaitAndGetNextResponse(1000)
25+
.assertThatNoErrorsArePresent()
26+
.assertThatField("$.data.testSubscription").asString().isEqualTo(FOO);
2827
}
2928

3029
void assertThatTestTemplateAutoConfigurationWorksCorrectly() throws IOException {
3130
// GIVEN
32-
final GraphQLTestTemplate graphQLTestTemplate
33-
= applicationContext.getBean(GraphQLTestTemplate.class);
34-
// WHEN
35-
final GraphQLResponse graphQLResponse
36-
= graphQLTestTemplate.postForResource("test-query.graphql");
37-
// THEN
38-
assertThat(graphQLResponse.get("$.data.testQuery")).isEqualTo(FOO);
31+
final GraphQLTestTemplate testTemplate = applicationContext.getBean(GraphQLTestTemplate.class);
32+
// WHEN - THEN
33+
testTemplate.postForResource("test-query.graphql")
34+
.assertThatNoErrorsArePresent()
35+
.assertThatField("$.data.testQuery").asString().isEqualTo(FOO);
3936
}
4037
}

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

Lines changed: 83 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
package com.graphql.spring.boot.test;
22

3+
import com.fasterxml.jackson.databind.JavaType;
34
import com.fasterxml.jackson.databind.JsonNode;
45
import com.fasterxml.jackson.databind.ObjectMapper;
6+
import com.graphql.spring.boot.test.assertions.GraphQLErrorListAssertion;
7+
import com.graphql.spring.boot.test.assertions.GraphQLFieldAssert;
8+
import com.graphql.spring.boot.test.assertions.NumberOfErrorsAssertion;
9+
import com.graphql.spring.boot.test.helper.GraphQLTestConstantsHelper;
510
import com.jayway.jsonpath.JsonPath;
611
import com.jayway.jsonpath.ReadContext;
12+
import org.springframework.boot.test.json.JsonContentAssert;
713
import org.springframework.http.HttpStatus;
814
import org.springframework.http.ResponseEntity;
915

@@ -30,17 +36,24 @@ public JsonNode readTree() throws IOException {
3036
return mapper.readTree(responseEntity.getBody());
3137
}
3238

39+
public Object getRaw(String path) {
40+
return get(path, Object.class);
41+
}
42+
3343
public String get(String path) {
3444
return get(path, String.class);
3545
}
3646

3747
public <T> T get(String path, Class<T> type) {
38-
return mapper.convertValue(context.read(path, Object.class), type);
48+
return mapper.convertValue(context.read(path), type);
49+
}
50+
51+
public <T> T get(String path, JavaType type) {
52+
return mapper.convertValue(context.read(path), type);
3953
}
4054

4155
public <T> List<T> getList(String path, Class<T> type) {
42-
final List<?> raw = context.read(path, List.class);
43-
return mapper.convertValue(raw, mapper.getTypeFactory().constructCollectionType(List.class, type));
56+
return get(path, mapper.getTypeFactory().constructCollectionType(List.class, type));
4457
}
4558

4659
public ReadContext context() {
@@ -58,4 +71,71 @@ public HttpStatus getStatusCode() {
5871
public ResponseEntity<String> getRawResponse() {
5972
return responseEntity;
6073
}
74+
75+
/**
76+
* Asserts that no errors are present in the response. An empty or null "errors" array also passes this test.
77+
* @return this object
78+
*/
79+
public GraphQLResponse assertThatNoErrorsArePresent() {
80+
return assertThatListOfErrors().hasNoErrors().and();
81+
}
82+
83+
/**
84+
* Returns an assertion for the number of errors in the response.
85+
* @return the assertion for the number of errors.
86+
*/
87+
public NumberOfErrorsAssertion assertThatNumberOfErrors() {
88+
return new NumberOfErrorsAssertion(this);
89+
}
90+
91+
/**
92+
* Returns an assertion for the list of errors in the response.
93+
* @return the assertion for the list of errors.
94+
*/
95+
public GraphQLErrorListAssertion assertThatListOfErrors() {
96+
return new GraphQLErrorListAssertion(this);
97+
}
98+
99+
/**
100+
* Returns an assertion for the given field(s).
101+
* @param jsonPath the JSON path of the field(s) to assert.
102+
* @return the assertion for the given field.
103+
*/
104+
public GraphQLFieldAssert assertThatField(final String jsonPath) {
105+
return new GraphQLFieldAssert(this, jsonPath);
106+
}
107+
108+
/**
109+
* Returns an assertion for the root "data" field.
110+
* @return the assertion for the $.data field.
111+
*/
112+
public GraphQLFieldAssert assertThatDataField() {
113+
return assertThatField(GraphQLTestConstantsHelper.DATA_PATH);
114+
}
115+
116+
/**
117+
* Returns an assertion for the root "extensions" field.
118+
* @return the assertion for the $.extensions field.
119+
*/
120+
public GraphQLFieldAssert assertThatExtensionsField() {
121+
return assertThatField(GraphQLTestConstantsHelper.EXTENSIONS_PATH);
122+
}
123+
124+
/**
125+
* Returns an assertion for the root "errors" field.
126+
* @return the assertion for the $.errors field.
127+
*/
128+
public GraphQLFieldAssert assertThatErrorsField() {
129+
return assertThatField(GraphQLTestConstantsHelper.ERRORS_PATH);
130+
}
131+
132+
/**
133+
* Returns an assertion for the JSON content of the response. Since the Spring Boot Framework does not provide
134+
* an abstract version of {@link JsonContentAssert} (as core AssertJ assertions do), it is not possible
135+
* chain other assertions after this one.
136+
* @return a {@link JsonContentAssert} instance for the content of the response.
137+
*/
138+
public JsonContentAssert assertThatJsonContent() {
139+
return new JsonContentAssert(null, responseEntity.getBody());
140+
}
61141
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package com.graphql.spring.boot.test;
2+
3+
import com.fasterxml.jackson.annotation.JsonTypeInfo;
4+
import graphql.ErrorClassification;
5+
import graphql.ErrorType;
6+
import graphql.GraphQLError;
7+
import graphql.language.SourceLocation;
8+
import lombok.AllArgsConstructor;
9+
import lombok.Builder;
10+
import lombok.Data;
11+
import lombok.NoArgsConstructor;
12+
import org.springframework.util.NumberUtils;
13+
14+
import java.util.List;
15+
import java.util.Map;
16+
import java.util.Optional;
17+
import java.util.stream.Collectors;
18+
19+
import static java.util.Objects.nonNull;
20+
21+
/**
22+
* An implementation of the {@link GraphQLError} interface for testing purposes.
23+
*/
24+
@Data
25+
@Builder
26+
@NoArgsConstructor
27+
@AllArgsConstructor
28+
public class GraphQLTestError implements GraphQLError {
29+
private String message;
30+
@JsonTypeInfo(defaultImpl = JacksonFriendlySourceLocation.class, use = JsonTypeInfo.Id.CLASS)
31+
private List<SourceLocation> locations;
32+
@JsonTypeInfo(defaultImpl = ErrorType.class, use = JsonTypeInfo.Id.CLASS)
33+
private ErrorClassification errorType;
34+
private List<Object> path;
35+
private Map<String, Object> extensions;
36+
37+
@Override
38+
public String toString() {
39+
final StringBuilder sb = new StringBuilder();
40+
sb.append(Optional.ofNullable(errorType).map(ErrorClassification::toString).orElse("<Unspecified error>"));
41+
sb.append(": ");
42+
sb.append(Optional.ofNullable(message).orElse("<error message not provided>"));
43+
if (nonNull(locations) && !locations.isEmpty()) {
44+
sb.append(" at line ");
45+
locations.forEach(
46+
location -> sb
47+
.append(location.getLine())
48+
.append(", column ")
49+
.append(location.getColumn()).append(" in ")
50+
.append(Optional.ofNullable(location.getSourceName()).orElse("unnamed/unspecified source"))
51+
);
52+
}
53+
if (nonNull(path) && !path.isEmpty()) {
54+
sb.append(". Selection path: ");
55+
sb.append(path.stream()
56+
.map(Object::toString)
57+
.map(this::toNumericIndexIfPossible)
58+
.collect(Collectors.joining("/"))
59+
.replaceAll("/\\[", "[")
60+
);
61+
}
62+
return sb.toString();
63+
}
64+
65+
private String toNumericIndexIfPossible(final String s) {
66+
try {
67+
return "[" + NumberUtils.parseNumber(s, Long.class) + "]";
68+
} catch (IllegalArgumentException e) {
69+
return s;
70+
}
71+
}
72+
}

0 commit comments

Comments
 (0)