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

feat: add fluent API support for test template and response #443

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;

@Configuration
@ConditionalOnWebApplication
Expand All @@ -16,8 +19,14 @@ public class GraphQLTestAutoConfiguration {

@Bean
@ConditionalOnMissingBean
public GraphQLTestTemplate graphQLTestUtils() {
return new GraphQLTestTemplate();
public GraphQLTestTemplate graphQLTestUtils(
final ResourceLoader resourceLoader,
final TestRestTemplate restTemplate,
@Value("${graphql.servlet.mapping:/graphql}")
final String graphqlMapping,
final ObjectMapper objectMapper
) {
return new GraphQLTestTemplate(resourceLoader, restTemplate, graphqlMapping, objectMapper);
}

@Bean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,20 @@ public class GraphQLTestAutoConfigurationTestBase {

void assertThatTestSubscriptionWorksCorrectly() {
// GIVEN
final GraphQLTestSubscription graphQLTestSubscription
= applicationContext.getBean(GraphQLTestSubscription.class);
// WHEN
final GraphQLResponse graphQLResponse
= graphQLTestSubscription.start("test-subscription.graphql").awaitAndGetNextResponse(1000);
// THEN
assertThat(graphQLResponse.get("$.data.testSubscription")).isEqualTo(FOO);
final GraphQLTestSubscription testSubscription = applicationContext.getBean(GraphQLTestSubscription.class);
// WHEN - THEN
testSubscription.start("test-subscription.graphql")
.awaitAndGetNextResponse(1000)
.assertThatNoErrorsArePresent()
.assertThatField("$.data.testSubscription").asString().isEqualTo(FOO);
}

void assertThatTestTemplateAutoConfigurationWorksCorrectly() throws IOException {
// GIVEN
final GraphQLTestTemplate graphQLTestTemplate
= applicationContext.getBean(GraphQLTestTemplate.class);
// WHEN
final GraphQLResponse graphQLResponse
= graphQLTestTemplate.postForResource("test-query.graphql");
// THEN
assertThat(graphQLResponse.get("$.data.testQuery")).isEqualTo(FOO);
final GraphQLTestTemplate testTemplate = applicationContext.getBean(GraphQLTestTemplate.class);
// WHEN - THEN
testTemplate.postForResource("test-query.graphql")
.assertThatNoErrorsArePresent()
.assertThatField("$.data.testQuery").asString().isEqualTo(FOO);
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
package com.graphql.spring.boot.test;

import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.graphql.spring.boot.test.assertions.GraphQLErrorListAssertion;
import com.graphql.spring.boot.test.assertions.GraphQLFieldAssert;
import com.graphql.spring.boot.test.assertions.NumberOfErrorsAssertion;
import com.graphql.spring.boot.test.helper.GraphQLTestConstantsHelper;
import com.jayway.jsonpath.JsonPath;
import com.jayway.jsonpath.ReadContext;
import org.springframework.boot.test.json.JsonContentAssert;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;

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

public Object getRaw(String path) {
return get(path, Object.class);
}

public String get(String path) {
return get(path, String.class);
}

public <T> T get(String path, Class<T> type) {
return mapper.convertValue(context.read(path, Object.class), type);
return mapper.convertValue(context.read(path), type);
}

public <T> T get(String path, JavaType type) {
return mapper.convertValue(context.read(path), type);
}

public <T> List<T> getList(String path, Class<T> type) {
final List<?> raw = context.read(path, List.class);
return mapper.convertValue(raw, mapper.getTypeFactory().constructCollectionType(List.class, type));
return get(path, mapper.getTypeFactory().constructCollectionType(List.class, type));
}

public ReadContext context() {
Expand All @@ -58,4 +71,71 @@ public HttpStatus getStatusCode() {
public ResponseEntity<String> getRawResponse() {
return responseEntity;
}

/**
* Asserts that no errors are present in the response. An empty or null "errors" array also passes this test.
* @return this object
*/
public GraphQLResponse assertThatNoErrorsArePresent() {
return assertThatListOfErrors().hasNoErrors().and();
}

/**
* Returns an assertion for the number of errors in the response.
* @return the assertion for the number of errors.
*/
public NumberOfErrorsAssertion assertThatNumberOfErrors() {
return new NumberOfErrorsAssertion(this);
}

/**
* Returns an assertion for the list of errors in the response.
* @return the assertion for the list of errors.
*/
public GraphQLErrorListAssertion assertThatListOfErrors() {
return new GraphQLErrorListAssertion(this);
}

/**
* Returns an assertion for the given field(s).
* @param jsonPath the JSON path of the field(s) to assert.
* @return the assertion for the given field.
*/
public GraphQLFieldAssert assertThatField(final String jsonPath) {
return new GraphQLFieldAssert(this, jsonPath);
}

/**
* Returns an assertion for the root "data" field.
* @return the assertion for the $.data field.
*/
public GraphQLFieldAssert assertThatDataField() {
return assertThatField(GraphQLTestConstantsHelper.DATA_PATH);
}

/**
* Returns an assertion for the root "extensions" field.
* @return the assertion for the $.extensions field.
*/
public GraphQLFieldAssert assertThatExtensionsField() {
return assertThatField(GraphQLTestConstantsHelper.EXTENSIONS_PATH);
}

/**
* Returns an assertion for the root "errors" field.
* @return the assertion for the $.errors field.
*/
public GraphQLFieldAssert assertThatErrorsField() {
return assertThatField(GraphQLTestConstantsHelper.ERRORS_PATH);
}

/**
* Returns an assertion for the JSON content of the response. Since the Spring Boot Framework does not provide
* an abstract version of {@link JsonContentAssert} (as core AssertJ assertions do), it is not possible
* chain other assertions after this one.
* @return a {@link JsonContentAssert} instance for the content of the response.
*/
public JsonContentAssert assertThatJsonContent() {
return new JsonContentAssert(null, responseEntity.getBody());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.graphql.spring.boot.test;

import com.fasterxml.jackson.annotation.JsonTypeInfo;
import graphql.ErrorClassification;
import graphql.ErrorType;
import graphql.GraphQLError;
import graphql.language.SourceLocation;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.util.NumberUtils;

import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

import static java.util.Objects.nonNull;

/**
* An implementation of the {@link GraphQLError} interface for testing purposes.
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class GraphQLTestError implements GraphQLError {
private String message;
@JsonTypeInfo(defaultImpl = JacksonFriendlySourceLocation.class, use = JsonTypeInfo.Id.CLASS)
private List<SourceLocation> locations;
@JsonTypeInfo(defaultImpl = ErrorType.class, use = JsonTypeInfo.Id.CLASS)
private ErrorClassification errorType;
private List<Object> path;
private Map<String, Object> extensions;

@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append(Optional.ofNullable(errorType).map(ErrorClassification::toString).orElse("<Unspecified error>"));
sb.append(": ");
sb.append(Optional.ofNullable(message).orElse("<error message not provided>"));
if (nonNull(locations) && !locations.isEmpty()) {
sb.append(" at line ");
locations.forEach(
location -> sb
.append(location.getLine())
.append(", column ")
.append(location.getColumn()).append(" in ")
.append(Optional.ofNullable(location.getSourceName()).orElse("unnamed/unspecified source"))
);
}
if (nonNull(path) && !path.isEmpty()) {
sb.append(". Selection path: ");
sb.append(path.stream()
.map(Object::toString)
.map(this::toNumericIndexIfPossible)
.collect(Collectors.joining("/"))
.replaceAll("/\\[", "[")
);
}
return sb.toString();
}

private String toNumericIndexIfPossible(final String s) {
try {
return "[" + NumberUtils.parseNumber(s, Long.class) + "]";
} catch (IllegalArgumentException e) {
return s;
}
}
}
Loading