Add GraphQL web support
This commit removes the controller components and instead moves the web support to specific auto-configurations. This commit also adds tests for both web stacks.
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
package org.springframework.boot.graphql.reactive;
|
||||
|
||||
import graphql.GraphQL;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||
import org.springframework.boot.graphql.GraphQLAutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.graphql.reactive.GraphQLHandler;
|
||||
import org.springframework.web.reactive.function.server.RouterFunction;
|
||||
import org.springframework.web.reactive.function.server.RouterFunctions;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
|
||||
@ConditionalOnClass(GraphQL.class)
|
||||
@ConditionalOnBean(GraphQL.Builder.class)
|
||||
@AutoConfigureAfter(GraphQLAutoConfiguration.class)
|
||||
public class GraphQLWebFluxAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public GraphQLHandler graphQLHandler(GraphQL.Builder graphQLBuilder) {
|
||||
return new GraphQLHandler(graphQLBuilder);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> graphQLQueryEndpoint(GraphQLHandler handler) {
|
||||
return RouterFunctions.route().POST("/graphql", handler::handle).build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
@NonNullApi
|
||||
@NonNullFields
|
||||
package org.springframework.graphql.servlet.components;
|
||||
package org.springframework.boot.graphql.reactive;
|
||||
|
||||
import org.springframework.lang.NonNullApi;
|
||||
import org.springframework.lang.NonNullFields;
|
||||
@@ -0,0 +1,41 @@
|
||||
package org.springframework.boot.graphql.servlet;
|
||||
|
||||
import graphql.GraphQL;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||
import org.springframework.boot.graphql.GraphQLAutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.graphql.servlet.GraphQLHandler;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.servlet.function.RouterFunction;
|
||||
import org.springframework.web.servlet.function.RouterFunctions;
|
||||
import org.springframework.web.servlet.function.ServerResponse;
|
||||
|
||||
import static org.springframework.web.servlet.function.RequestPredicates.accept;
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnWebApplication
|
||||
@ConditionalOnClass(GraphQL.class)
|
||||
@ConditionalOnBean(GraphQL.Builder.class)
|
||||
@AutoConfigureAfter(GraphQLAutoConfiguration.class)
|
||||
public class GraphQLWebAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public GraphQLHandler graphQLHandler(GraphQL.Builder graphQLBuilder) {
|
||||
return new GraphQLHandler(graphQLBuilder);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> graphQLQueryEndpoint(GraphQLHandler handler) {
|
||||
return RouterFunctions.route()
|
||||
.POST("/graphql", accept(MediaType.APPLICATION_JSON), handler::handle)
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
@NonNullApi
|
||||
@NonNullFields
|
||||
package org.springframework.graphql.reactive.components;
|
||||
package org.springframework.boot.graphql.servlet;
|
||||
|
||||
import org.springframework.lang.NonNullApi;
|
||||
import org.springframework.lang.NonNullFields;
|
||||
@@ -1,86 +0,0 @@
|
||||
package org.springframework.graphql.reactive.components;
|
||||
|
||||
|
||||
import graphql.GraphQL;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.graphql.DefaultGraphQLInterceptor;
|
||||
import org.springframework.graphql.GraphQLHandler;
|
||||
import org.springframework.graphql.GraphQLHttpRequest;
|
||||
import org.springframework.graphql.GraphQLInterceptor;
|
||||
import org.springframework.web.reactive.function.server.RouterFunction;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
|
||||
|
||||
@Configuration
|
||||
public class GraphQLController {
|
||||
|
||||
@Autowired
|
||||
GraphQL graphQL;
|
||||
|
||||
GraphQLHandler graphQLHandler;
|
||||
|
||||
@Autowired(required = false)
|
||||
GraphQLInterceptor graphQLInterceptor;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
GraphQLInterceptor interceptor = graphQLInterceptor == null ? new DefaultGraphQLInterceptor() : graphQLInterceptor;
|
||||
this.graphQLHandler = new GraphQLHandler(graphQL, interceptor);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> routerFunction() {
|
||||
RouterFunction<ServerResponse> route = route()
|
||||
.POST("/graphql", this::graphqlPOST)
|
||||
.build();
|
||||
return route;
|
||||
|
||||
}
|
||||
|
||||
private Mono<ServerResponse> graphqlPOST(ServerRequest serverRequest) {
|
||||
Mono<GraphQLReactiveRequestBody> bodyMono = serverRequest.bodyToMono(GraphQLReactiveRequestBody.class);
|
||||
return bodyMono.flatMap(body -> {
|
||||
String query = body.getQuery();
|
||||
if (query == null) {
|
||||
query = "";
|
||||
}
|
||||
Map<String, Object> variables = body.getVariables();
|
||||
if (variables == null) {
|
||||
variables = Collections.emptyMap();
|
||||
}
|
||||
GraphQLHttpRequest graphQLHttpRequest = new GraphQLHttpRequest(
|
||||
query,
|
||||
body.getOperationName(),
|
||||
variables,
|
||||
serverRequest.headers().asHttpHeaders(),
|
||||
serverRequest.queryParams());
|
||||
return graphQLHandler.graphqlPOST(graphQLHttpRequest);
|
||||
}).flatMap(graphQLHttpResponse -> {
|
||||
//TODO: this should be handled better:
|
||||
// we don't want to serialize `null` values for `errors` and `extensions`
|
||||
// this is why we convert it to a Map here
|
||||
Map<String, Object> responseBodyRaw = new LinkedHashMap<>();
|
||||
responseBodyRaw.put("data", graphQLHttpResponse.getData());
|
||||
if (graphQLHttpResponse.getErrors() != null) {
|
||||
responseBodyRaw.put("errors", graphQLHttpResponse.getErrors());
|
||||
}
|
||||
if (graphQLHttpResponse.getExtensions() != null) {
|
||||
responseBodyRaw.put("extensions", graphQLHttpResponse.getExtensions());
|
||||
}
|
||||
return ServerResponse.ok().headers(httpHeaders -> {
|
||||
httpHeaders.addAll(graphQLHttpResponse.getHttpHeaders());
|
||||
}).bodyValue(responseBodyRaw);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package org.springframework.graphql.reactive.components;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class GraphQLReactiveRequestBody {
|
||||
private String query;
|
||||
private String operationName;
|
||||
private Map<String, Object> variables;
|
||||
|
||||
public String getQuery() {
|
||||
return query;
|
||||
}
|
||||
|
||||
public void setQuery(String query) {
|
||||
this.query = query;
|
||||
}
|
||||
|
||||
public String getOperationName() {
|
||||
return operationName;
|
||||
}
|
||||
|
||||
public void setOperationName(String operationName) {
|
||||
this.operationName = operationName;
|
||||
}
|
||||
|
||||
public Map<String, Object> getVariables() {
|
||||
return variables;
|
||||
}
|
||||
|
||||
public void setVariables(Map<String, Object> variables) {
|
||||
this.variables = variables;
|
||||
}
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
package org.springframework.graphql.servlet.components;
|
||||
|
||||
|
||||
import graphql.GraphQL;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.graphql.DefaultGraphQLInterceptor;
|
||||
import org.springframework.graphql.GraphQLHandler;
|
||||
import org.springframework.graphql.GraphQLHttpRequest;
|
||||
import org.springframework.graphql.GraphQLInterceptor;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.servlet.function.RequestPredicates;
|
||||
import org.springframework.web.servlet.function.RouterFunction;
|
||||
import org.springframework.web.servlet.function.ServerRequest;
|
||||
import org.springframework.web.servlet.function.ServerResponse;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.servlet.ServletException;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.springframework.web.servlet.function.RouterFunctions.route;
|
||||
|
||||
@Configuration
|
||||
public class GraphQLController {
|
||||
|
||||
@Autowired
|
||||
GraphQL graphQL;
|
||||
|
||||
GraphQLHandler graphQLHandler;
|
||||
|
||||
@Autowired(required = false)
|
||||
GraphQLInterceptor graphQLInterceptor;
|
||||
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
GraphQLInterceptor interceptor = graphQLInterceptor == null ? new DefaultGraphQLInterceptor() : graphQLInterceptor;
|
||||
this.graphQLHandler = new GraphQLHandler(graphQL, interceptor);
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> routerFunction() {
|
||||
RouterFunction<ServerResponse> route = route()
|
||||
.POST("/graphql", RequestPredicates.contentType(MediaType.APPLICATION_JSON)
|
||||
.or(RequestPredicates.contentType(MediaType.APPLICATION_JSON_UTF8)), this::graphqlPOST)
|
||||
.build();
|
||||
return route;
|
||||
|
||||
}
|
||||
|
||||
private ServerResponse graphqlPOST(ServerRequest serverRequest) {
|
||||
GraphQLServletRequestBody body = null;
|
||||
try {
|
||||
body = serverRequest.body(GraphQLServletRequestBody.class);
|
||||
} catch (ServletException | IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
String query = body.getQuery();
|
||||
if (query == null) {
|
||||
query = "";
|
||||
}
|
||||
Map<String, Object> variables = body.getVariables();
|
||||
if (variables == null) {
|
||||
variables = Collections.emptyMap();
|
||||
}
|
||||
|
||||
GraphQLHttpRequest graphQLHttpRequest = new GraphQLHttpRequest(
|
||||
query,
|
||||
body.getOperationName(),
|
||||
variables,
|
||||
serverRequest.headers().asHttpHeaders(),
|
||||
serverRequest.params());
|
||||
|
||||
Mono<Map<String, Object>> responseRawMono = graphQLHandler.graphqlPOST(graphQLHttpRequest)
|
||||
.map(graphQLHttpResponse -> {
|
||||
//TODO: this should be handled better:
|
||||
// we don't want to serialize `null` values for `errors` and `extensions`
|
||||
// this is why we convert it to a Map here
|
||||
Map<String, Object> responseBodyRaw = new LinkedHashMap<>();
|
||||
responseBodyRaw.put("data", graphQLHttpResponse.getData());
|
||||
if (graphQLHttpResponse.getErrors() != null) {
|
||||
responseBodyRaw.put("errors", graphQLHttpResponse.getErrors());
|
||||
}
|
||||
if (graphQLHttpResponse.getExtensions() != null) {
|
||||
responseBodyRaw.put("extensions", graphQLHttpResponse.getExtensions());
|
||||
}
|
||||
return responseBodyRaw;
|
||||
});
|
||||
//TODO: how to add http headers for the response here? It is part of responseRawMono but
|
||||
// ServerResponse don't accept a CompletableFuture or Mono as headers.
|
||||
return ServerResponse.ok().body(responseRawMono);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package org.springframework.graphql.servlet.components;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class GraphQLServletRequestBody {
|
||||
private String query;
|
||||
private String operationName;
|
||||
private Map<String, Object> variables;
|
||||
|
||||
public String getQuery() {
|
||||
return query;
|
||||
}
|
||||
|
||||
public void setQuery(String query) {
|
||||
this.query = query;
|
||||
}
|
||||
|
||||
public String getOperationName() {
|
||||
return operationName;
|
||||
}
|
||||
|
||||
public void setOperationName(String operationName) {
|
||||
this.operationName = operationName;
|
||||
}
|
||||
|
||||
public Map<String, Object> getVariables() {
|
||||
return variables;
|
||||
}
|
||||
|
||||
public void setVariables(Map<String, Object> variables) {
|
||||
this.variables = variables;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package org.springframework.boot.graphql;
|
||||
|
||||
public class Book {
|
||||
|
||||
String id;
|
||||
|
||||
String name;
|
||||
|
||||
int pageCount;
|
||||
|
||||
String author;
|
||||
|
||||
public Book() {
|
||||
}
|
||||
|
||||
public Book(String id, String name, int pageCount, String author) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.pageCount = pageCount;
|
||||
this.author = author;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public int getPageCount() {
|
||||
return pageCount;
|
||||
}
|
||||
|
||||
public void setPageCount(int pageCount) {
|
||||
this.pageCount = pageCount;
|
||||
}
|
||||
|
||||
public String getAuthor() {
|
||||
return author;
|
||||
}
|
||||
|
||||
public void setAuthor(String author) {
|
||||
this.author = author;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.springframework.boot.graphql;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import graphql.schema.DataFetcher;
|
||||
|
||||
public class GraphQLDataFetchers {
|
||||
|
||||
private static List<Book> books = Arrays.asList(
|
||||
new Book("book-1", "GraphQL for beginners", 100, "John GraphQL"),
|
||||
new Book("book-2", "Harry Potter and the Philosopher's Stone", 223, "Joanne Rowling"),
|
||||
new Book("book-3", "Moby Dick", 635, "Moby Dick"),
|
||||
new Book("book-3", "Moby Dick", 635, "Moby Dick"));
|
||||
|
||||
|
||||
public static DataFetcher getBookByIdDataFetcher() {
|
||||
return dataFetchingEnvironment -> {
|
||||
String bookId = dataFetchingEnvironment.getArgument("id");
|
||||
return books
|
||||
.stream()
|
||||
.filter(book -> book.getId().equals(bookId))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package org.springframework.boot.graphql.reactive;
|
||||
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration;
|
||||
import org.springframework.boot.graphql.GraphQLAutoConfiguration;
|
||||
import org.springframework.boot.graphql.GraphQLDataFetchers;
|
||||
import org.springframework.boot.graphql.RuntimeWiringCustomizer;
|
||||
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
|
||||
import static graphql.schema.idl.TypeRuntimeWiring.newTypeWiring;
|
||||
|
||||
class GraphQLWebFluxEndpointTests {
|
||||
|
||||
@Test
|
||||
void endpointHandlesGraphQLQueries() throws Exception {
|
||||
new ReactiveWebApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(JacksonAutoConfiguration.class, CodecsAutoConfiguration.class,
|
||||
WebFluxAutoConfiguration.class, HttpHandlerAutoConfiguration.class,
|
||||
GraphQLAutoConfiguration.class, GraphQLWebFluxAutoConfiguration.class))
|
||||
.withUserConfiguration(DataFetchersConfiguration.class)
|
||||
.withPropertyValues("spring.main.web-application-type=reactive", "spring.graphql.schema:classpath:books/schema.graphqls").run((context) -> {
|
||||
WebTestClient client = createWebTestClient(context);
|
||||
|
||||
String query = "{" +
|
||||
" bookById(id: \\\"book-1\\\"){ " +
|
||||
" id" +
|
||||
" name" +
|
||||
" pageCount" +
|
||||
" author" +
|
||||
" }" +
|
||||
"}";
|
||||
|
||||
String body = "{" +
|
||||
" \"query\": \"" + query + "\"" +
|
||||
"}";
|
||||
|
||||
client.post().uri("/graphql").contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)
|
||||
.bodyValue(body).exchange().expectStatus().isOk()
|
||||
.expectBody()
|
||||
.jsonPath("data.bookById.name").isEqualTo("GraphQL for beginners");
|
||||
});
|
||||
}
|
||||
|
||||
private WebTestClient createWebTestClient(ApplicationContext context) {
|
||||
return WebTestClient.bindToApplicationContext(context).configureClient().baseUrl("https://spring.example.org")
|
||||
.build();
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class DataFetchersConfiguration {
|
||||
|
||||
@Bean
|
||||
public RuntimeWiringCustomizer bookDataFetcher() {
|
||||
return (runtimeWiring) -> runtimeWiring.type(newTypeWiring("Query")
|
||||
.dataFetcher("bookById", GraphQLDataFetchers.getBookByIdDataFetcher()));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package org.springframework.boot.graphql.servlet;
|
||||
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
|
||||
import org.springframework.boot.graphql.GraphQLAutoConfiguration;
|
||||
import org.springframework.boot.graphql.GraphQLDataFetchers;
|
||||
import org.springframework.boot.graphql.RuntimeWiringCustomizer;
|
||||
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
|
||||
import static graphql.schema.idl.TypeRuntimeWiring.newTypeWiring;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
|
||||
class GraphQLWebEndpointTests {
|
||||
|
||||
@Test
|
||||
void endpointHandlesGraphQLQueries() throws Exception {
|
||||
new WebApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class,
|
||||
WebMvcAutoConfiguration.class, DispatcherServletAutoConfiguration.class,
|
||||
GraphQLAutoConfiguration.class, GraphQLWebAutoConfiguration.class))
|
||||
.withUserConfiguration(DataFetchersConfiguration.class)
|
||||
.withPropertyValues("spring.main.web-application-type=servlet", "spring.graphql.schema:classpath:books/schema.graphqls").run((context) -> {
|
||||
|
||||
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
|
||||
String query = "{" +
|
||||
" bookById(id: \\\"book-1\\\"){ " +
|
||||
" id" +
|
||||
" name" +
|
||||
" pageCount" +
|
||||
" author" +
|
||||
" }" +
|
||||
"}";
|
||||
|
||||
String body = "{" +
|
||||
" \"query\": \"" + query + "\"" +
|
||||
"}";
|
||||
|
||||
mockMvc.perform(post("/graphql").content(body).contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
|
||||
.andExpect(jsonPath("data.bookById.name").value("GraphQL for beginners"));
|
||||
});
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class DataFetchersConfiguration {
|
||||
|
||||
@Bean
|
||||
public RuntimeWiringCustomizer bookDataFetcher() {
|
||||
return (runtimeWiring) -> runtimeWiring.type(newTypeWiring("Query")
|
||||
.dataFetcher("bookById", GraphQLDataFetchers.getBookByIdDataFetcher()));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
10
spring-graphql-web/src/test/resources/books/schema.graphqls
Normal file
10
spring-graphql-web/src/test/resources/books/schema.graphqls
Normal file
@@ -0,0 +1,10 @@
|
||||
type Query {
|
||||
bookById(id: ID): Book
|
||||
}
|
||||
|
||||
type Book {
|
||||
id: ID
|
||||
name: String
|
||||
pageCount: Int
|
||||
author: String
|
||||
}
|
||||
Reference in New Issue
Block a user