From 81754c8bc4171ac959d3ed0f5c5f13345ae4caac Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Mon, 7 Mar 2022 21:05:11 +0100 Subject: [PATCH 1/5] Upgrade to Spring GraphQL 1.0.0-SNAPSHOT This commit switches to 1.0.0-SNAPSHOT for Spring GraphQL, before its upcoming 1.0.0-M6 version. This commit adapts to the changes introduced in spring-projects/spring-graphql#317 : now that `GraphQlClient` has been introduced, `GraphQlTester` has been aligned with the new infrastructure. The `@GraphQlTest` and `@SpringBootTest` testing support is now using different variants for each. All samples have been updated to use the proper GraphQL terminology, see and spring-projects/spring-graphql#310 . See gh-29637 --- ...aphQlWebFluxSecurityAutoConfiguration.java | 2 +- ...raphQlWebMvcSecurityAutoConfiguration.java | 4 +- ...lQueryByExampleAutoConfigurationTests.java | 8 ++-- ...GraphQlQuerydslAutoConfigurationTests.java | 7 ++-- ...eQueryByExampleAutoConfigurationTests.java | 7 ++-- ...eactiveQuerydslAutoConfigurationTests.java | 7 ++-- ...WebFluxSecurityAutoConfigurationTests.java | 2 +- ...lWebMvcSecurityAutoConfigurationTests.java | 4 +- .../spring-boot-dependencies/build.gradle | 2 +- .../src/docs/asciidoc/features/testing.adoc | 11 ++++- .../GraphQlIntegrationTests.java | 40 ++++++++++++++++++ .../GreetingControllerTests.java | 4 +- .../GraphQlIntegrationTests.kt | 42 +++++++++++++++++++ .../GreetingControllerTests.kt | 4 +- .../autoconfigure/graphql/GraphQlTest.java | 8 ++-- ...va => AutoConfigureHttpGraphQlTester.java} | 10 ++--- .../GraphQlTesterAutoConfiguration.java | 5 ++- ...> HttpGraphQlTesterAutoConfiguration.java} | 9 ++-- ...ter.AutoConfigureHttpGraphQlTester.imports | 2 + ...ster.AutoConfigureWebGraphQlTester.imports | 2 - .../graphql/GraphQlTestIntegrationTest.java | 4 +- .../boot/test/context/SpringBootTest.java | 15 +++++-- ...> HttpGraphQlTesterContextCustomizer.java} | 38 ++++++++--------- ...raphQlTesterContextCustomizerFactory.java} | 14 +++---- .../main/resources/META-INF/spring.factories | 2 +- ...terContextCustomizerIntegrationTests.java} | 10 ++--- ...extCustomizerWithCustomBasePathTests.java} | 10 ++--- ...CustomizerWithCustomContextPathTests.java} | 10 ++--- .../graphql/GreetingControllerTests.java | 30 ++++++++----- .../graphql/ProjectControllerTests.java | 4 +- 30 files changed, 213 insertions(+), 104 deletions(-) create mode 100644 spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/springgraphqltests/GraphQlIntegrationTests.java create mode 100644 spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/springgraphqltests/GraphQlIntegrationTests.kt rename spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/graphql/tester/{AutoConfigureWebGraphQlTester.java => AutoConfigureHttpGraphQlTester.java} (87%) rename spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/graphql/tester/{WebGraphQlTesterAutoConfiguration.java => HttpGraphQlTesterAutoConfiguration.java} (84%) create mode 100644 spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.graphql.tester.AutoConfigureHttpGraphQlTester.imports delete mode 100644 spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.graphql.tester.AutoConfigureWebGraphQlTester.imports rename spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/graphql/tester/{WebGraphQlTesterContextCustomizer.java => HttpGraphQlTesterContextCustomizer.java} (84%) rename spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/graphql/tester/{WebGraphQlTesterContextCustomizerFactory.java => HttpGraphQlTesterContextCustomizerFactory.java} (76%) rename spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/graphql/tester/{WebGraphQlTesterContextCustomizerIntegrationTests.java => HttpGraphQlTesterContextCustomizerIntegrationTests.java} (90%) rename spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/graphql/tester/{WebGraphQlTesterContextCustomizerWithCustomBasePathTests.java => HttpGraphQlTesterContextCustomizerWithCustomBasePathTests.java} (90%) rename spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/graphql/tester/{WebGraphQlTesterContextCustomizerWithCustomContextPathTests.java => HttpGraphQlTesterContextCustomizerWithCustomContextPathTests.java} (87%) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/security/GraphQlWebFluxSecurityAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/security/GraphQlWebFluxSecurityAutoConfiguration.java index 1d0ce465d10..4bc6b567a1f 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/security/GraphQlWebFluxSecurityAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/security/GraphQlWebFluxSecurityAutoConfiguration.java @@ -26,7 +26,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.graphql.reactive.GraphQlWebFluxAutoConfiguration; import org.springframework.context.annotation.Bean; -import org.springframework.graphql.security.ReactiveSecurityDataFetcherExceptionResolver; +import org.springframework.graphql.execution.ReactiveSecurityDataFetcherExceptionResolver; import org.springframework.graphql.web.webflux.GraphQlHttpHandler; import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/security/GraphQlWebMvcSecurityAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/security/GraphQlWebMvcSecurityAutoConfiguration.java index 90f283710c2..5a3ae3cd437 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/security/GraphQlWebMvcSecurityAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/security/GraphQlWebMvcSecurityAutoConfiguration.java @@ -26,8 +26,8 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.graphql.servlet.GraphQlWebMvcAutoConfiguration; import org.springframework.context.annotation.Bean; -import org.springframework.graphql.security.SecurityContextThreadLocalAccessor; -import org.springframework.graphql.security.SecurityDataFetcherExceptionResolver; +import org.springframework.graphql.execution.SecurityContextThreadLocalAccessor; +import org.springframework.graphql.execution.SecurityDataFetcherExceptionResolver; import org.springframework.graphql.web.webmvc.GraphQlHttpHandler; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlQueryByExampleAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlQueryByExampleAutoConfigurationTests.java index be28b3e908e..c2dafc6ed9f 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlQueryByExampleAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlQueryByExampleAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,7 +30,7 @@ import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.query.QueryByExampleExecutor; import org.springframework.graphql.GraphQlService; import org.springframework.graphql.data.GraphQlRepository; -import org.springframework.graphql.test.tester.GraphQlTester; +import org.springframework.graphql.test.tester.GraphQlServiceTester; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; @@ -55,8 +55,8 @@ class GraphQlQueryByExampleAutoConfigurationTests { void shouldRegisterDataFetcherForQueryByExampleRepositories() { this.contextRunner.run((context) -> { GraphQlService graphQlService = context.getBean(GraphQlService.class); - GraphQlTester graphQlTester = GraphQlTester.create(graphQlService); - graphQlTester.query("{ bookById(id: 1) {name}}").execute().path("bookById.name").entity(String.class) + GraphQlServiceTester graphQlTester = GraphQlServiceTester.create(graphQlService); + graphQlTester.document("{ bookById(id: 1) {name}}").execute().path("bookById.name").entity(String.class) .isEqualTo("Test title"); }); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlQuerydslAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlQuerydslAutoConfigurationTests.java index c687d0834e2..9e7e34663d2 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlQuerydslAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlQuerydslAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,6 +30,7 @@ import org.springframework.data.querydsl.QuerydslPredicateExecutor; import org.springframework.data.repository.CrudRepository; import org.springframework.graphql.GraphQlService; import org.springframework.graphql.data.GraphQlRepository; +import org.springframework.graphql.test.tester.GraphQlServiceTester; import org.springframework.graphql.test.tester.GraphQlTester; import static org.mockito.ArgumentMatchers.any; @@ -55,8 +56,8 @@ class GraphQlQuerydslAutoConfigurationTests { void shouldRegisterDataFetcherForQueryDslRepositories() { this.contextRunner.run((context) -> { GraphQlService graphQlService = context.getBean(GraphQlService.class); - GraphQlTester graphQlTester = GraphQlTester.create(graphQlService); - graphQlTester.query("{ bookById(id: 1) {name}}").execute().path("bookById.name").entity(String.class) + GraphQlTester graphQlTester = GraphQlServiceTester.create(graphQlService); + graphQlTester.document("{ bookById(id: 1) {name}}").execute().path("bookById.name").entity(String.class) .isEqualTo("Test title"); }); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlReactiveQueryByExampleAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlReactiveQueryByExampleAutoConfigurationTests.java index 20eb88e7973..ed87b22be0c 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlReactiveQueryByExampleAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlReactiveQueryByExampleAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,6 +29,7 @@ import org.springframework.data.repository.query.ReactiveQueryByExampleExecutor; import org.springframework.data.repository.reactive.ReactiveCrudRepository; import org.springframework.graphql.GraphQlService; import org.springframework.graphql.data.GraphQlRepository; +import org.springframework.graphql.test.tester.GraphQlServiceTester; import org.springframework.graphql.test.tester.GraphQlTester; import static org.mockito.ArgumentMatchers.any; @@ -54,8 +55,8 @@ class GraphQlReactiveQueryByExampleAutoConfigurationTests { void shouldRegisterDataFetcherForQueryByExampleRepositories() { this.contextRunner.run((context) -> { GraphQlService graphQlService = context.getBean(GraphQlService.class); - GraphQlTester graphQlTester = GraphQlTester.create(graphQlService); - graphQlTester.query("{ bookById(id: 1) {name}}").execute().path("bookById.name").entity(String.class) + GraphQlTester graphQlTester = GraphQlServiceTester.create(graphQlService); + graphQlTester.document("{ bookById(id: 1) {name}}").execute().path("bookById.name").entity(String.class) .isEqualTo("Test title"); }); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlReactiveQuerydslAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlReactiveQuerydslAutoConfigurationTests.java index 4d5d461462c..dcefb935ee8 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlReactiveQuerydslAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlReactiveQuerydslAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,6 +29,7 @@ import org.springframework.data.querydsl.ReactiveQuerydslPredicateExecutor; import org.springframework.data.repository.reactive.ReactiveCrudRepository; import org.springframework.graphql.GraphQlService; import org.springframework.graphql.data.GraphQlRepository; +import org.springframework.graphql.test.tester.GraphQlServiceTester; import org.springframework.graphql.test.tester.GraphQlTester; import static org.mockito.ArgumentMatchers.any; @@ -54,8 +55,8 @@ class GraphQlReactiveQuerydslAutoConfigurationTests { void shouldRegisterDataFetcherForQueryDslRepositories() { this.contextRunner.run((context) -> { GraphQlService graphQlService = context.getBean(GraphQlService.class); - GraphQlTester graphQlTester = GraphQlTester.create(graphQlService); - graphQlTester.query("{ bookById(id: 1) {name}}").execute().path("bookById.name").entity(String.class) + GraphQlTester graphQlTester = GraphQlServiceTester.create(graphQlService); + graphQlTester.document("{ bookById(id: 1) {name}}").execute().path("bookById.name").entity(String.class) .isEqualTo("Test title"); }); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/security/GraphQlWebFluxSecurityAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/security/GraphQlWebFluxSecurityAutoConfigurationTests.java index 9b6f1db534a..dbe21765c4f 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/security/GraphQlWebFluxSecurityAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/security/GraphQlWebFluxSecurityAutoConfigurationTests.java @@ -37,8 +37,8 @@ import org.springframework.boot.test.context.runner.ReactiveWebApplicationContex import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.graphql.execution.ErrorType; +import org.springframework.graphql.execution.ReactiveSecurityDataFetcherExceptionResolver; import org.springframework.graphql.execution.RuntimeWiringConfigurer; -import org.springframework.graphql.security.ReactiveSecurityDataFetcherExceptionResolver; import org.springframework.http.MediaType; import org.springframework.lang.Nullable; import org.springframework.security.access.prepost.PreAuthorize; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/security/GraphQlWebMvcSecurityAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/security/GraphQlWebMvcSecurityAutoConfigurationTests.java index fc030d9d453..fa33b554397 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/security/GraphQlWebMvcSecurityAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/security/GraphQlWebMvcSecurityAutoConfigurationTests.java @@ -34,8 +34,8 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.graphql.execution.ErrorType; import org.springframework.graphql.execution.RuntimeWiringConfigurer; -import org.springframework.graphql.security.SecurityContextThreadLocalAccessor; -import org.springframework.graphql.security.SecurityDataFetcherExceptionResolver; +import org.springframework.graphql.execution.SecurityContextThreadLocalAccessor; +import org.springframework.graphql.execution.SecurityDataFetcherExceptionResolver; import org.springframework.http.MediaType; import org.springframework.lang.Nullable; import org.springframework.security.access.prepost.PreAuthorize; diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index b2d678f5bce..29e1b354c13 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -1682,7 +1682,7 @@ bom { ] } } - library("Spring GraphQL", "1.0.0-M5") { + library("Spring GraphQL", "1.0.0-SNAPSHOT") { group("org.springframework.graphql") { modules = [ "spring-graphql", diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/testing.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/testing.adoc index b19a813ae0a..b160e51cfc7 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/testing.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/testing.adoc @@ -435,8 +435,12 @@ Spring GraphQL offers a dedicated testing support module; you'll need to add it } ---- -This testing module ships the {spring-graphql-docs}/testing.html#testing-webgraphqltester[WebGraphQlTester]. +This testing module ships the {spring-graphql-docs}/#testing-graphqltester[GraphQlTester]. The tester is heavily used in test, so be sure to become familiar with using it. +There are `GraphQlTester` variants and Spring Boot will auto-configure them depending on the type of tests: + +* the `GraphQlServiceTester` performs tests on the server side, without a client nor a transport +* the `HttpGraphQlTester` performs tests with a client that connects to a server, with or without a live server Spring Boot helps you to test your {spring-graphql-docs}#controllers[Spring GraphQL Controllers] with the `@GraphQlTest` annotation. `@GraphQlTest` auto-configures the Spring GraphQL infrastructure, without any transport nor server being involved. @@ -452,8 +456,11 @@ Often, `@GraphQlTest` is limited to a set of controllers and used in combination include::code:GreetingControllerTests[] -TIP: You can also auto-configure `WebGraphQlTester` in a non-`@GraphQlTest` (such as `@SpringBootTest`) by annotating it with `@AutoConfigureWebGraphQlTester`. +`@SpringBootTest` tests are full integration tests and involve the entire application. +When using a random or defined port, a live server is configured and an `HttpGraphQlTester` bean is contributed automatically so you can use it to test your server. +When a MOCK environment is configured, you can also request an `HttpGraphQlTester` bean by annotating your test class with `@AutoConfigureHttpGraphQlTester`: +include::code:GraphQlIntegrationTests[] [[features.testing.spring-boot-applications.autoconfigured-spring-data-cassandra]] diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/springgraphqltests/GraphQlIntegrationTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/springgraphqltests/GraphQlIntegrationTests.java new file mode 100644 index 00000000000..2ff2a08ea66 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/springgraphqltests/GraphQlIntegrationTests.java @@ -0,0 +1,40 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.features.testing.springbootapplications.springgraphqltests; + +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.graphql.tester.AutoConfigureHttpGraphQlTester; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.graphql.test.tester.HttpGraphQlTester; + +@AutoConfigureHttpGraphQlTester +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK) +class GraphQlIntegrationTests { + + @Test + void shouldGreetWithSpecificName(@Autowired HttpGraphQlTester graphQlTester) { + HttpGraphQlTester authenticatedTester = graphQlTester.mutate() + .webTestClient( + (client) -> client.defaultHeaders((headers) -> headers.setBasicAuth("admin", "ilovespring"))) + .build(); + authenticatedTester.document("{ greeting(name: \"Alice\") } ").execute().path("greeting").entity(String.class) + .isEqualTo("Hello, Alice!"); + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/springgraphqltests/GreetingControllerTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/springgraphqltests/GreetingControllerTests.java index 932003390b3..951bd126d08 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/springgraphqltests/GreetingControllerTests.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/springgraphqltests/GreetingControllerTests.java @@ -31,13 +31,13 @@ class GreetingControllerTests { @Test void shouldGreetWithSpecificName() { - this.graphQlTester.query("{ greeting(name: \"Alice\") } ").execute().path("greeting").entity(String.class) + this.graphQlTester.document("{ greeting(name: \"Alice\") } ").execute().path("greeting").entity(String.class) .isEqualTo("Hello, Alice!"); } @Test void shouldGreetWithDefaultName() { - this.graphQlTester.query("{ greeting } ").execute().path("greeting").entity(String.class) + this.graphQlTester.document("{ greeting } ").execute().path("greeting").entity(String.class) .isEqualTo("Hello, Spring!"); } diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/springgraphqltests/GraphQlIntegrationTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/springgraphqltests/GraphQlIntegrationTests.kt new file mode 100644 index 00000000000..f8b0f3ea094 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/springgraphqltests/GraphQlIntegrationTests.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.features.testing.springbootapplications.springgraphqltests + +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.autoconfigure.graphql.tester.AutoConfigureHttpGraphQlTester +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.graphql.test.tester.HttpGraphQlTester +import org.springframework.http.HttpHeaders +import org.springframework.test.web.reactive.server.WebTestClient + +@AutoConfigureHttpGraphQlTester +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK) +class GraphQlIntegrationTests { + + @Test + fun shouldGreetWithSpecificName(@Autowired graphQlTester: HttpGraphQlTester) { + val authenticatedTester = graphQlTester.mutate() + .webTestClient { client: WebTestClient.Builder -> + client.defaultHeaders { headers: HttpHeaders -> + headers.setBasicAuth("admin", "ilovespring") + } + }.build() + authenticatedTester.document("{ greeting(name: \"Alice\") } ").execute() + .path("greeting").entity(String::class.java).isEqualTo("Hello, Alice!") + } +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/springgraphqltests/GreetingControllerTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/springgraphqltests/GreetingControllerTests.kt index 5f678663945..902d9cec26a 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/springgraphqltests/GreetingControllerTests.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/springgraphqltests/GreetingControllerTests.kt @@ -30,13 +30,13 @@ internal class GreetingControllerTests { @Test fun shouldGreetWithSpecificName() { - graphQlTester.query("{ greeting(name: \"Alice\") } ").execute().path("greeting").entity(String::class.java) + graphQlTester.document("{ greeting(name: \"Alice\") } ").execute().path("greeting").entity(String::class.java) .isEqualTo("Hello, Alice!") } @Test fun shouldGreetWithDefaultName() { - graphQlTester.query("{ greeting } ").execute().path("greeting").entity(String::class.java) + graphQlTester.document("{ greeting } ").execute().path("greeting").entity(String::class.java) .isEqualTo("Hello, Spring!") } diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/graphql/GraphQlTest.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/graphql/GraphQlTest.java index d0cfa2d55e0..c096f037ba3 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/graphql/GraphQlTest.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/graphql/GraphQlTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 the original author or authors. + * Copyright 2020-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,7 +31,7 @@ import org.springframework.boot.test.autoconfigure.OverrideAutoConfiguration; import org.springframework.boot.test.autoconfigure.core.AutoConfigureCache; import org.springframework.boot.test.autoconfigure.filter.TypeExcludeFilters; import org.springframework.boot.test.autoconfigure.graphql.tester.AutoConfigureGraphQlTester; -import org.springframework.boot.test.autoconfigure.graphql.tester.AutoConfigureWebGraphQlTester; +import org.springframework.boot.test.autoconfigure.graphql.tester.AutoConfigureHttpGraphQlTester; import org.springframework.boot.test.autoconfigure.json.AutoConfigureJson; import org.springframework.context.annotation.ComponentScan; import org.springframework.core.annotation.AliasFor; @@ -67,9 +67,9 @@ import org.springframework.test.context.junit.jupiter.SpringExtension; * and other components required for the tests. *

* To load your full application configuration instead and test via - * {@code WebGraphQlTester}, consider using + * {@code HttpGraphQlTester}, consider using * {@link org.springframework.boot.test.context.SpringBootTest @SpringBootTest} combined - * with {@link AutoConfigureWebGraphQlTester @AutoConfigureWebGraphQlTester}. + * with {@link AutoConfigureHttpGraphQlTester @AutoConfigureHttpGraphQlTester}. * * @author Brian Clozel * @since 2.7.0 diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/graphql/tester/AutoConfigureWebGraphQlTester.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/graphql/tester/AutoConfigureHttpGraphQlTester.java similarity index 87% rename from spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/graphql/tester/AutoConfigureWebGraphQlTester.java rename to spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/graphql/tester/AutoConfigureHttpGraphQlTester.java index 5c5a8e1bd6b..133db41f402 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/graphql/tester/AutoConfigureWebGraphQlTester.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/graphql/tester/AutoConfigureHttpGraphQlTester.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 the original author or authors. + * Copyright 2020-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,10 +26,10 @@ import java.lang.annotation.Target; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.graphql.test.tester.WebGraphQlTester; +import org.springframework.graphql.test.tester.HttpGraphQlTester; /** - * Annotation that can be applied to a test class to enable a {@link WebGraphQlTester}. + * Annotation that can be applied to a test class to enable a {@link HttpGraphQlTester}. * *

* This annotation should be used with @@ -38,7 +38,7 @@ import org.springframework.graphql.test.tester.WebGraphQlTester; * * @author Brian Clozel * @since 2.7.0 - * @see WebGraphQlTesterAutoConfiguration + * @see HttpGraphQlTesterAutoConfiguration */ @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @@ -47,6 +47,6 @@ import org.springframework.graphql.test.tester.WebGraphQlTester; @AutoConfigureMockMvc @AutoConfigureWebTestClient @ImportAutoConfiguration -public @interface AutoConfigureWebGraphQlTester { +public @interface AutoConfigureHttpGraphQlTester { } diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/graphql/tester/GraphQlTesterAutoConfiguration.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/graphql/tester/GraphQlTesterAutoConfiguration.java index 76caa61c22c..d2e654ee794 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/graphql/tester/GraphQlTesterAutoConfiguration.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/graphql/tester/GraphQlTesterAutoConfiguration.java @@ -25,6 +25,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean import org.springframework.boot.autoconfigure.graphql.GraphQlAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.graphql.GraphQlService; +import org.springframework.graphql.test.tester.GraphQlServiceTester; import org.springframework.graphql.test.tester.GraphQlTester; /** @@ -40,8 +41,8 @@ public class GraphQlTesterAutoConfiguration { @Bean @ConditionalOnBean(GraphQlService.class) @ConditionalOnMissingBean - public GraphQlTester graphQlTester(GraphQlService graphQlService) { - return GraphQlTester.create(graphQlService); + public GraphQlServiceTester graphQlTester(GraphQlService graphQlService) { + return GraphQlServiceTester.create(graphQlService); } } diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/graphql/tester/WebGraphQlTesterAutoConfiguration.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/graphql/tester/HttpGraphQlTesterAutoConfiguration.java similarity index 84% rename from spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/graphql/tester/WebGraphQlTesterAutoConfiguration.java rename to spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/graphql/tester/HttpGraphQlTesterAutoConfiguration.java index e3f65198cb8..953571a1c9b 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/graphql/tester/WebGraphQlTesterAutoConfiguration.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/graphql/tester/HttpGraphQlTesterAutoConfiguration.java @@ -24,26 +24,27 @@ import org.springframework.boot.autoconfigure.graphql.GraphQlProperties; import org.springframework.boot.test.autoconfigure.web.reactive.WebTestClientAutoConfiguration; import org.springframework.boot.test.autoconfigure.web.servlet.MockMvcAutoConfiguration; import org.springframework.context.annotation.Bean; +import org.springframework.graphql.test.tester.HttpGraphQlTester; import org.springframework.graphql.test.tester.WebGraphQlTester; import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.web.reactive.function.client.WebClient; /** - * Auto-configuration for {@link WebGraphQlTester}. + * Auto-configuration for {@link HttpGraphQlTester}. * * @author Brian Clozel * @since 2.7.0 */ @AutoConfiguration(after = { WebTestClientAutoConfiguration.class, MockMvcAutoConfiguration.class }) @ConditionalOnClass({ WebClient.class, WebTestClient.class, WebGraphQlTester.class }) -public class WebGraphQlTesterAutoConfiguration { +public class HttpGraphQlTesterAutoConfiguration { @Bean @ConditionalOnBean(WebTestClient.class) @ConditionalOnMissingBean - public WebGraphQlTester webTestClientGraphQlTester(WebTestClient webTestClient, GraphQlProperties properties) { + public HttpGraphQlTester webTestClientGraphQlTester(WebTestClient webTestClient, GraphQlProperties properties) { WebTestClient mutatedWebTestClient = webTestClient.mutate().baseUrl(properties.getPath()).build(); - return WebGraphQlTester.create(mutatedWebTestClient); + return HttpGraphQlTester.create(mutatedWebTestClient); } } diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.graphql.tester.AutoConfigureHttpGraphQlTester.imports b/spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.graphql.tester.AutoConfigureHttpGraphQlTester.imports new file mode 100644 index 00000000000..78f1ffda4df --- /dev/null +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.graphql.tester.AutoConfigureHttpGraphQlTester.imports @@ -0,0 +1,2 @@ +# AutoConfigureHttpGraphQlTester auto-configuration imports +org.springframework.boot.test.autoconfigure.graphql.tester.HttpGraphQlTesterAutoConfiguration \ No newline at end of file diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.graphql.tester.AutoConfigureWebGraphQlTester.imports b/spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.graphql.tester.AutoConfigureWebGraphQlTester.imports deleted file mode 100644 index 9b6b835e16c..00000000000 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.graphql.tester.AutoConfigureWebGraphQlTester.imports +++ /dev/null @@ -1,2 +0,0 @@ -# AutoConfigureWebGraphQlTester auto-configuration imports -org.springframework.boot.test.autoconfigure.graphql.tester.WebGraphQlTesterAutoConfiguration \ No newline at end of file diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/graphql/GraphQlTestIntegrationTest.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/graphql/GraphQlTestIntegrationTest.java index 18807b332bb..fe0cca64a24 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/graphql/GraphQlTestIntegrationTest.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/graphql/GraphQlTestIntegrationTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 the original author or authors. + * Copyright 2020-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,7 +35,7 @@ public class GraphQlTestIntegrationTest { @Test void getBookdByIdShouldReturnTestBook() { String query = "{ bookById(id: \"book-1\"){ id name pageCount author } }"; - this.graphQlTester.query(query).execute().path("data.bookById.id").entity(String.class).isEqualTo("42"); + this.graphQlTester.document(query).execute().path("data.bookById.id").entity(String.class).isEqualTo("42"); } } diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTest.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTest.java index d069745393f..c75b1f3eefa 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTest.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTest.java @@ -62,10 +62,17 @@ import org.springframework.web.context.WebApplicationContext; * including the ability to start a fully running web server listening on a * {@link WebEnvironment#DEFINED_PORT defined} or {@link WebEnvironment#RANDOM_PORT * random} port. - *

  • Registers a {@link org.springframework.boot.test.web.client.TestRestTemplate - * TestRestTemplate} and/or - * {@link org.springframework.test.web.reactive.server.WebTestClient WebTestClient} bean - * for use in web tests that are using a fully running web server.
  • + * + *

    + * Can register the following beans for web tests that are using a fully running web + * server: + *

    * * @author Phillip Webb diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/graphql/tester/WebGraphQlTesterContextCustomizer.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/graphql/tester/HttpGraphQlTesterContextCustomizer.java similarity index 84% rename from spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/graphql/tester/WebGraphQlTesterContextCustomizer.java rename to spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/graphql/tester/HttpGraphQlTesterContextCustomizer.java index 53135a1fd38..ed0a836ab3b 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/graphql/tester/WebGraphQlTesterContextCustomizer.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/graphql/tester/HttpGraphQlTesterContextCustomizer.java @@ -35,7 +35,7 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.Ordered; -import org.springframework.graphql.test.tester.WebGraphQlTester; +import org.springframework.graphql.test.tester.HttpGraphQlTester; import org.springframework.test.context.ContextCustomizer; import org.springframework.test.context.MergedContextConfiguration; import org.springframework.test.context.TestContextAnnotationUtils; @@ -45,32 +45,32 @@ import org.springframework.util.StringUtils; import org.springframework.web.context.WebApplicationContext; /** - * {@link ContextCustomizer} for {@link WebGraphQlTester}. + * {@link ContextCustomizer} for {@link HttpGraphQlTester}. * * @author Brian Clozel */ -class WebGraphQlTesterContextCustomizer implements ContextCustomizer { +class HttpGraphQlTesterContextCustomizer implements ContextCustomizer { @Override public void customizeContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) { SpringBootTest springBootTest = TestContextAnnotationUtils.findMergedAnnotation(mergedConfig.getTestClass(), SpringBootTest.class); if (springBootTest.webEnvironment().isEmbedded()) { - registerWebGraphQlTester(context); + registerHttpGraphQlTester(context); } } - private void registerWebGraphQlTester(ConfigurableApplicationContext context) { + private void registerHttpGraphQlTester(ConfigurableApplicationContext context) { ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); if (beanFactory instanceof BeanDefinitionRegistry) { - registerWebGraphQlTester((BeanDefinitionRegistry) beanFactory); + registerHttpGraphQlTester((BeanDefinitionRegistry) beanFactory); } } - private void registerWebGraphQlTester(BeanDefinitionRegistry registry) { - RootBeanDefinition definition = new RootBeanDefinition(WebGraphQlTesterRegistrar.class); + private void registerHttpGraphQlTester(BeanDefinitionRegistry registry) { + RootBeanDefinition definition = new RootBeanDefinition(HttpGraphQlTesterRegistrar.class); definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); - registry.registerBeanDefinition(WebGraphQlTesterRegistrar.class.getName(), definition); + registry.registerBeanDefinition(HttpGraphQlTesterRegistrar.class.getName(), definition); } @Override @@ -83,7 +83,7 @@ class WebGraphQlTesterContextCustomizer implements ContextCustomizer { return getClass().hashCode(); } - private static class WebGraphQlTesterRegistrar + private static class HttpGraphQlTesterRegistrar implements BeanDefinitionRegistryPostProcessor, Ordered, BeanFactoryAware { private BeanFactory beanFactory; @@ -96,9 +96,9 @@ class WebGraphQlTesterContextCustomizer implements ContextCustomizer { @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors((ListableBeanFactory) this.beanFactory, - WebGraphQlTester.class, false, false).length == 0) { - registry.registerBeanDefinition(WebGraphQlTester.class.getName(), - new RootBeanDefinition(WebGraphQlTesterFactory.class)); + HttpGraphQlTester.class, false, false).length == 0) { + registry.registerBeanDefinition(HttpGraphQlTester.class.getName(), + new RootBeanDefinition(HttpGraphQlTesterFactory.class)); } } @@ -114,7 +114,7 @@ class WebGraphQlTesterContextCustomizer implements ContextCustomizer { } - public static class WebGraphQlTesterFactory implements FactoryBean, ApplicationContextAware { + public static class HttpGraphQlTesterFactory implements FactoryBean, ApplicationContextAware { private static final String SERVLET_APPLICATION_CONTEXT_CLASS = "org.springframework.web.context.WebApplicationContext"; @@ -122,7 +122,7 @@ class WebGraphQlTesterContextCustomizer implements ContextCustomizer { private ApplicationContext applicationContext; - private WebGraphQlTester object; + private HttpGraphQlTester object; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { @@ -136,23 +136,23 @@ class WebGraphQlTesterContextCustomizer implements ContextCustomizer { @Override public Class getObjectType() { - return WebGraphQlTester.class; + return HttpGraphQlTester.class; } @Override - public WebGraphQlTester getObject() throws Exception { + public HttpGraphQlTester getObject() throws Exception { if (this.object == null) { this.object = createGraphQlTester(); } return this.object; } - private WebGraphQlTester createGraphQlTester() { + private HttpGraphQlTester createGraphQlTester() { WebTestClient webTestClient = this.applicationContext.getBean(WebTestClient.class); boolean sslEnabled = isSslEnabled(this.applicationContext); String port = this.applicationContext.getEnvironment().getProperty("local.server.port", "8080"); WebTestClient mutatedWebClient = webTestClient.mutate().baseUrl(getBaseUrl(sslEnabled, port)).build(); - return WebGraphQlTester.create(mutatedWebClient); + return HttpGraphQlTester.create(mutatedWebClient); } private String getBaseUrl(boolean sslEnabled, String port) { diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/graphql/tester/WebGraphQlTesterContextCustomizerFactory.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/graphql/tester/HttpGraphQlTesterContextCustomizerFactory.java similarity index 76% rename from spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/graphql/tester/WebGraphQlTesterContextCustomizerFactory.java rename to spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/graphql/tester/HttpGraphQlTesterContextCustomizerFactory.java index d19cca1b7ec..ac245832b5e 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/graphql/tester/WebGraphQlTesterContextCustomizerFactory.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/graphql/tester/HttpGraphQlTesterContextCustomizerFactory.java @@ -19,7 +19,7 @@ package org.springframework.boot.test.graphql.tester; import java.util.List; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.graphql.test.tester.GraphQlTester; +import org.springframework.graphql.test.tester.HttpGraphQlTester; import org.springframework.test.context.ContextConfigurationAttributes; import org.springframework.test.context.ContextCustomizer; import org.springframework.test.context.ContextCustomizerFactory; @@ -27,14 +27,14 @@ import org.springframework.test.context.TestContextAnnotationUtils; import org.springframework.util.ClassUtils; /** - * {@link ContextCustomizerFactory} for {@link GraphQlTester}. + * {@link ContextCustomizerFactory} for {@link HttpGraphQlTester}. * * @author Brian Clozel - * @see WebGraphQlTesterContextCustomizer + * @see HttpGraphQlTesterContextCustomizer */ -class WebGraphQlTesterContextCustomizerFactory implements ContextCustomizerFactory { +class HttpGraphQlTesterContextCustomizerFactory implements ContextCustomizerFactory { - private static final String WEBGRAPHQLTESTER_CLASS = "org.springframework.graphql.test.tester.WebGraphQlTester"; + private static final String HTTPGRAPHQLTESTER_CLASS = "org.springframework.graphql.test.tester.HttpGraphQlTester"; private static final String WEBTESTCLIENT_CLASS = "org.springframework.test.web.reactive.server.WebTestClient"; @@ -43,12 +43,12 @@ class WebGraphQlTesterContextCustomizerFactory implements ContextCustomizerFacto List configAttributes) { SpringBootTest springBootTest = TestContextAnnotationUtils.findMergedAnnotation(testClass, SpringBootTest.class); - return (springBootTest != null && isGraphQlTesterPresent()) ? new WebGraphQlTesterContextCustomizer() : null; + return (springBootTest != null && isGraphQlTesterPresent()) ? new HttpGraphQlTesterContextCustomizer() : null; } private boolean isGraphQlTesterPresent() { return ClassUtils.isPresent(WEBTESTCLIENT_CLASS, getClass().getClassLoader()) - && ClassUtils.isPresent(WEBGRAPHQLTESTER_CLASS, getClass().getClassLoader()); + && ClassUtils.isPresent(HTTPGRAPHQLTESTER_CLASS, getClass().getClassLoader()); } } diff --git a/spring-boot-project/spring-boot-test/src/main/resources/META-INF/spring.factories b/spring-boot-project/spring-boot-test/src/main/resources/META-INF/spring.factories index a0dac2ca693..44cbf9b58d7 100644 --- a/spring-boot-project/spring-boot-test/src/main/resources/META-INF/spring.factories +++ b/spring-boot-project/spring-boot-test/src/main/resources/META-INF/spring.factories @@ -2,7 +2,7 @@ org.springframework.test.context.ContextCustomizerFactory=\ org.springframework.boot.test.context.ImportsContextCustomizerFactory,\ org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizerFactory,\ -org.springframework.boot.test.graphql.tester.WebGraphQlTesterContextCustomizerFactory,\ +org.springframework.boot.test.graphql.tester.HttpGraphQlTesterContextCustomizerFactory,\ org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory,\ org.springframework.boot.test.mock.mockito.MockitoContextCustomizerFactory,\ org.springframework.boot.test.web.client.TestRestTemplateContextCustomizerFactory,\ diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/graphql/tester/WebGraphQlTesterContextCustomizerIntegrationTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/graphql/tester/HttpGraphQlTesterContextCustomizerIntegrationTests.java similarity index 90% rename from spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/graphql/tester/WebGraphQlTesterContextCustomizerIntegrationTests.java rename to spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/graphql/tester/HttpGraphQlTesterContextCustomizerIntegrationTests.java index cc0ff3299a3..b66eec19be3 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/graphql/tester/WebGraphQlTesterContextCustomizerIntegrationTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/graphql/tester/HttpGraphQlTesterContextCustomizerIntegrationTests.java @@ -28,7 +28,7 @@ import org.springframework.boot.web.embedded.tomcat.TomcatReactiveWebServerFacto import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.buffer.DefaultDataBufferFactory; -import org.springframework.graphql.test.tester.WebGraphQlTester; +import org.springframework.graphql.test.tester.HttpGraphQlTester; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.server.reactive.ContextPathCompositeHandler; @@ -38,21 +38,21 @@ import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.test.annotation.DirtiesContext; /** - * Integration test for {@link WebGraphQlTesterContextCustomizer}. + * Integration test for {@link HttpGraphQlTesterContextCustomizer}. * * @author Brian Clozel */ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = "spring.main.web-application-type=reactive") @DirtiesContext -class WebGraphQlTesterContextCustomizerIntegrationTests { +class HttpGraphQlTesterContextCustomizerIntegrationTests { @Autowired - WebGraphQlTester graphQlTester; + HttpGraphQlTester graphQlTester; @Test void shouldHandleGraphQlRequests() { - this.graphQlTester.query("{}").executeAndVerify(); + this.graphQlTester.document("{}").executeAndVerify(); } @Configuration(proxyBeanMethods = false) diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/graphql/tester/WebGraphQlTesterContextCustomizerWithCustomBasePathTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/graphql/tester/HttpGraphQlTesterContextCustomizerWithCustomBasePathTests.java similarity index 90% rename from spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/graphql/tester/WebGraphQlTesterContextCustomizerWithCustomBasePathTests.java rename to spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/graphql/tester/HttpGraphQlTesterContextCustomizerWithCustomBasePathTests.java index 0f232849806..f7167d07e10 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/graphql/tester/WebGraphQlTesterContextCustomizerWithCustomBasePathTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/graphql/tester/HttpGraphQlTesterContextCustomizerWithCustomBasePathTests.java @@ -28,7 +28,7 @@ import org.springframework.boot.web.embedded.tomcat.TomcatReactiveWebServerFacto import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.buffer.DefaultDataBufferFactory; -import org.springframework.graphql.test.tester.WebGraphQlTester; +import org.springframework.graphql.test.tester.HttpGraphQlTester; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.server.reactive.ContextPathCompositeHandler; @@ -38,21 +38,21 @@ import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.test.context.TestPropertySource; /** - * Tests for {@link WebGraphQlTesterContextCustomizer} with a custom context path for a + * Tests for {@link HttpGraphQlTesterContextCustomizer} with a custom context path for a * Reactive web application. * * @author Brian Clozel */ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @TestPropertySource(properties = { "spring.main.web-application-type=reactive", "spring.webflux.base-path=/test" }) -class WebGraphQlTesterContextCustomizerWithCustomBasePathTests { +class HttpGraphQlTesterContextCustomizerWithCustomBasePathTests { @Autowired - WebGraphQlTester graphQlTester; + HttpGraphQlTester graphQlTester; @Test void shouldHandleGraphQlRequests() { - this.graphQlTester.query("{}").executeAndVerify(); + this.graphQlTester.document("{}").executeAndVerify(); } @Configuration(proxyBeanMethods = false) diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/graphql/tester/WebGraphQlTesterContextCustomizerWithCustomContextPathTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/graphql/tester/HttpGraphQlTesterContextCustomizerWithCustomContextPathTests.java similarity index 87% rename from spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/graphql/tester/WebGraphQlTesterContextCustomizerWithCustomContextPathTests.java rename to spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/graphql/tester/HttpGraphQlTesterContextCustomizerWithCustomContextPathTests.java index ae795399095..fec60ac4424 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/graphql/tester/WebGraphQlTesterContextCustomizerWithCustomContextPathTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/graphql/tester/HttpGraphQlTesterContextCustomizerWithCustomContextPathTests.java @@ -24,7 +24,7 @@ import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactor import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; -import org.springframework.graphql.test.tester.WebGraphQlTester; +import org.springframework.graphql.test.tester.HttpGraphQlTester; import org.springframework.http.MediaType; import org.springframework.test.context.TestPropertySource; import org.springframework.web.bind.annotation.PostMapping; @@ -32,21 +32,21 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.DispatcherServlet; /** - * Tests for {@link WebGraphQlTesterContextCustomizer} with a custom context path for a + * Tests for {@link HttpGraphQlTesterContextCustomizer} with a custom context path for a * Servlet web application. * * @author Brian Clozel */ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @TestPropertySource(properties = "server.servlet.context-path=/test") -class WebGraphQlTesterContextCustomizerWithCustomContextPathTests { +class HttpGraphQlTesterContextCustomizerWithCustomContextPathTests { @Autowired - WebGraphQlTester graphQlTester; + HttpGraphQlTester graphQlTester; @Test void shouldHandleGraphQlRequests() { - this.graphQlTester.query("{}").executeAndVerify(); + this.graphQlTester.document("{}").executeAndVerify(); } @Configuration(proxyBeanMethods = false) diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-graphql/src/test/java/smoketest/graphql/GreetingControllerTests.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-graphql/src/test/java/smoketest/graphql/GreetingControllerTests.java index 75c9565c68a..27ddea4659a 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-graphql/src/test/java/smoketest/graphql/GreetingControllerTests.java +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-graphql/src/test/java/smoketest/graphql/GreetingControllerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,23 +19,23 @@ package smoketest.graphql; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.graphql.tester.AutoConfigureWebGraphQlTester; +import org.springframework.boot.test.autoconfigure.graphql.tester.AutoConfigureHttpGraphQlTester; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.graphql.execution.ErrorType; -import org.springframework.graphql.test.tester.WebGraphQlTester; +import org.springframework.graphql.test.tester.HttpGraphQlTester; import static org.assertj.core.api.Assertions.assertThat; @SpringBootTest -@AutoConfigureWebGraphQlTester +@AutoConfigureHttpGraphQlTester class GreetingControllerTests { @Autowired - private WebGraphQlTester graphQlTester; + private HttpGraphQlTester graphQlTester; @Test void shouldUnauthorizeAnonymousUsers() { - this.graphQlTester.queryName("greeting").variable("name", "Brian").execute().errors().satisfy((errors) -> { + this.graphQlTester.documentName("greeting").variable("name", "Brian").execute().errors().satisfy((errors) -> { assertThat(errors).hasSize(1); assertThat(errors.get(0).getErrorType()).isEqualTo(ErrorType.UNAUTHORIZED); }); @@ -43,15 +43,23 @@ class GreetingControllerTests { @Test void shouldGreetWithSpecificName() { - this.graphQlTester.queryName("greeting").variable("name", "Brian") - .httpHeaders((headers) -> headers.setBasicAuth("admin", "admin")).execute().path("greeting") - .entity(String.class).isEqualTo("Hello, Brian!"); + HttpGraphQlTester authenticated = withAdminCredentials(this.graphQlTester); + authenticated.documentName("greeting").variable("name", "Brian").execute().path("greeting").entity(String.class) + .isEqualTo("Hello, Brian!"); } @Test void shouldGreetWithDefaultName() { - this.graphQlTester.query("{ greeting }").httpHeaders((headers) -> headers.setBasicAuth("admin", "admin")) - .execute().path("greeting").entity(String.class).isEqualTo("Hello, Spring!"); + HttpGraphQlTester authenticated = withAdminCredentials(this.graphQlTester); + authenticated.document("{ greeting }").execute().path("greeting").entity(String.class) + .isEqualTo("Hello, Spring!"); + } + + private HttpGraphQlTester withAdminCredentials(HttpGraphQlTester graphQlTester) { + return graphQlTester.mutate() + .webTestClient( + (httpClient) -> httpClient.defaultHeaders((headers) -> headers.setBasicAuth("admin", "admin"))) + .build(); } } diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-graphql/src/test/java/smoketest/graphql/ProjectControllerTests.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-graphql/src/test/java/smoketest/graphql/ProjectControllerTests.java index 3601b1eea52..0341b1d5f64 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-graphql/src/test/java/smoketest/graphql/ProjectControllerTests.java +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-graphql/src/test/java/smoketest/graphql/ProjectControllerTests.java @@ -30,13 +30,13 @@ class ProjectControllerTests { @Test void shouldFindSpringGraphQl() { - this.graphQlTester.query("{ project(slug: \"spring-graphql\") { name } }").execute().path("project.name") + this.graphQlTester.document("{ project(slug: \"spring-graphql\") { name } }").execute().path("project.name") .entity(String.class).isEqualTo("Spring GraphQL"); } @Test void shouldNotFindUnknownProject() { - this.graphQlTester.query("{ project(slug: \"spring-unknown\") { name } }").execute().path("project.name") + this.graphQlTester.document("{ project(slug: \"spring-unknown\") { name } }").execute().path("project.name") .valueDoesNotExist(); } From ef4add27f07feada23f6f4d878ddca5ad6ec3696 Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Thu, 3 Feb 2022 10:41:35 +0000 Subject: [PATCH 2/5] Do not set ConversionService on AnnotatedControllerConfigurer After https://github.com/spring-projects/spring-graphql/commit/0b449d89e1d9e8754d42ad5e5af7f780c705ce32, the ConversionService on AnnotatedControllerConfigurer is an internally managed instance that is customized with FormatterRegistrar rather than set. Closes gh-29636 --- .../boot/autoconfigure/graphql/GraphQlAutoConfiguration.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/GraphQlAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/GraphQlAutoConfiguration.java index f12d28c8b0a..6ff1a923770 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/GraphQlAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/GraphQlAutoConfiguration.java @@ -39,7 +39,6 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.context.annotation.Bean; import org.springframework.core.io.Resource; import org.springframework.core.io.support.ResourcePatternResolver; -import org.springframework.format.support.DefaultFormattingConversionService; import org.springframework.graphql.GraphQlService; import org.springframework.graphql.data.method.annotation.support.AnnotatedControllerConfigurer; import org.springframework.graphql.execution.BatchLoaderRegistry; @@ -130,9 +129,7 @@ public class GraphQlAutoConfiguration { @Bean @ConditionalOnMissingBean public AnnotatedControllerConfigurer annotatedControllerConfigurer() { - AnnotatedControllerConfigurer annotatedControllerConfigurer = new AnnotatedControllerConfigurer(); - annotatedControllerConfigurer.setConversionService(new DefaultFormattingConversionService()); - return annotatedControllerConfigurer; + return new AnnotatedControllerConfigurer(); } private List toList(ObjectProvider provider) { From bf79d6baefbe93d4599e6601b44676912be977c3 Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Tue, 8 Mar 2022 14:47:18 +0100 Subject: [PATCH 3/5] Auto-configure ConversionService for GraphQL Prior to this commit, we would configure a default conversion service for GraphQL annotated controllers, without registering any additional formatters. This commit aligns the GraphQL auto-configuration with MVC and WebFlux, since we now use the `ApplicationConversionService` to register all application formatters for the GraphQL infrastructure. Closes gh-29638 --- .../graphql/GraphQlAutoConfiguration.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/GraphQlAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/GraphQlAutoConfiguration.java index 6ff1a923770..1599bbf89a8 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/GraphQlAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/GraphQlAutoConfiguration.java @@ -30,12 +30,14 @@ import graphql.schema.visibility.NoIntrospectionGraphqlFieldVisibility; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.convert.ApplicationConversionService; import org.springframework.context.annotation.Bean; import org.springframework.core.io.Resource; import org.springframework.core.io.support.ResourcePatternResolver; @@ -63,6 +65,12 @@ public class GraphQlAutoConfiguration { private static final Log logger = LogFactory.getLog(GraphQlAutoConfiguration.class); + private final ListableBeanFactory beanFactory; + + public GraphQlAutoConfiguration(ListableBeanFactory beanFactory) { + this.beanFactory = beanFactory; + } + @Bean @ConditionalOnMissingBean public GraphQlSource graphQlSource(ResourcePatternResolver resourcePatternResolver, GraphQlProperties properties, @@ -129,7 +137,10 @@ public class GraphQlAutoConfiguration { @Bean @ConditionalOnMissingBean public AnnotatedControllerConfigurer annotatedControllerConfigurer() { - return new AnnotatedControllerConfigurer(); + AnnotatedControllerConfigurer controllerConfigurer = new AnnotatedControllerConfigurer(); + controllerConfigurer + .addFormatterRegistrar((registry) -> ApplicationConversionService.addBeans(registry, this.beanFactory)); + return controllerConfigurer; } private List toList(ObjectProvider provider) { From 087e853c5de4f3d6bfbad526435cd26d3f9cd93a Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Tue, 8 Mar 2022 17:38:28 +0100 Subject: [PATCH 4/5] Refine GraphQL server auto-configuration Prior to this commit, launching a GraphQL application without any schema file or customizer bean would result in an exception caught by a FailureAnalyzer telling the developer about configured locations. Since then, a new client has been introduced in Spring GraphQL and the mere presence of the GraphQL starter does not mean anymore that the intent is to create a GraphQL API in the app: we could instead just consume an existing, remote API. This commit refines the GraphQL server auto-configuration so that it is enabled only if: * there is at least one schema file in the configured locations * or a `GraphQlSourceCustomizer` bean has been defined in the app These changes make the custom FailureAnalyzer useless and is also removed as part of this commit. Closes gh-30035 --- .../graphql/ConditionalOnGraphQlSchema.java | 40 +++++++ .../DefaultGraphQlSchemaCondition.java | 109 ++++++++++++++++++ .../graphql/GraphQlAutoConfiguration.java | 9 +- .../InvalidSchemaLocationsException.java | 101 ---------------- ...hemaLocationsExceptionFailureAnalyzer.java | 42 ------- .../main/resources/META-INF/spring.factories | 1 - .../DefaultGraphQlSchemaConditionTests.java | 105 +++++++++++++++++ .../GraphQlAutoConfigurationTests.java | 9 +- ...ocationsExceptionFailureAnalyzerTests.java | 69 ----------- .../InvalidSchemaLocationsExceptionTests.java | 71 ------------ 10 files changed, 259 insertions(+), 297 deletions(-) create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/ConditionalOnGraphQlSchema.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/DefaultGraphQlSchemaCondition.java delete mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/InvalidSchemaLocationsException.java delete mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/InvalidSchemaLocationsExceptionFailureAnalyzer.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/DefaultGraphQlSchemaConditionTests.java delete mode 100644 spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/InvalidSchemaLocationsExceptionFailureAnalyzerTests.java delete mode 100644 spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/InvalidSchemaLocationsExceptionTests.java diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/ConditionalOnGraphQlSchema.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/ConditionalOnGraphQlSchema.java new file mode 100644 index 00000000000..205a81ac933 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/ConditionalOnGraphQlSchema.java @@ -0,0 +1,40 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.graphql; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.context.annotation.Conditional; + +/** + * {@link Conditional @Conditional} that only matches when a GraphQL schema is defined for + * the application, via schema files or infrastructure beans. + * + * @author Brian Clozel + * @since 2.7.0 + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Conditional(DefaultGraphQlSchemaCondition.class) +public @interface ConditionalOnGraphQlSchema { + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/DefaultGraphQlSchemaCondition.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/DefaultGraphQlSchemaCondition.java new file mode 100644 index 00000000000..c3680eb2768 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/DefaultGraphQlSchemaCondition.java @@ -0,0 +1,109 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.graphql; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.boot.autoconfigure.condition.ConditionMessage; +import org.springframework.boot.autoconfigure.condition.ConditionOutcome; +import org.springframework.boot.autoconfigure.condition.SpringBootCondition; +import org.springframework.boot.context.properties.bind.Binder; +import org.springframework.context.annotation.Condition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.context.annotation.ConfigurationCondition; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.ResourcePatternResolver; +import org.springframework.core.io.support.ResourcePatternUtils; +import org.springframework.core.type.AnnotatedTypeMetadata; + +/** + * {@link Condition} that checks whether a GraphQL schema has been defined in the + * application. This is looking for: + *
      + *
    • schema files in the {@link GraphQlProperties configured locations}
    • + *
    • or infrastructure beans such as {@link GraphQlSourceBuilderCustomizer}
    • + *
    + * + * @author Brian Clozel + * @see ConditionalOnGraphQlSchema + */ +class DefaultGraphQlSchemaCondition extends SpringBootCondition implements ConfigurationCondition { + + @Override + public ConfigurationCondition.ConfigurationPhase getConfigurationPhase() { + return ConfigurationCondition.ConfigurationPhase.REGISTER_BEAN; + } + + @Override + public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { + boolean match = false; + List messages = new ArrayList<>(2); + ConditionMessage.Builder message = ConditionMessage.forCondition(ConditionalOnGraphQlSchema.class); + + Binder binder = Binder.get(context.getEnvironment()); + GraphQlProperties.Schema schema = binder.bind("spring.graphql.schema", GraphQlProperties.Schema.class) + .orElse(new GraphQlProperties.Schema()); + ResourcePatternResolver resourcePatternResolver = ResourcePatternUtils + .getResourcePatternResolver(context.getResourceLoader()); + List schemaResources = resolveSchemaResources(resourcePatternResolver, schema.getLocations(), + schema.getFileExtensions()); + if (!schemaResources.isEmpty()) { + match = true; + messages.add(message.found("schema", "schemas").items(ConditionMessage.Style.QUOTE, schemaResources)); + } + else { + messages.add(message.didNotFind("schema files in locations").items(ConditionMessage.Style.QUOTE, + Arrays.asList(schema.getLocations()))); + } + ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); + String[] customizerBeans = beanFactory.getBeanNamesForType(GraphQlSourceBuilderCustomizer.class, false, false); + if (customizerBeans.length != 0) { + match = true; + messages.add(message.found("customizer", "customizers").items(Arrays.asList(customizerBeans))); + } + else { + messages.add((message.didNotFind("GraphQlSourceBuilderCustomizer").atAll())); + } + return new ConditionOutcome(match, ConditionMessage.of(messages)); + } + + private List resolveSchemaResources(ResourcePatternResolver resolver, String[] locations, + String[] extensions) { + List resources = new ArrayList<>(); + for (String location : locations) { + for (String extension : extensions) { + resources.addAll(resolveSchemaResources(resolver, location + "*" + extension)); + } + } + return resources; + } + + private List resolveSchemaResources(ResourcePatternResolver resolver, String pattern) { + try { + return Arrays.asList(resolver.getResources(pattern)); + } + catch (IOException ex) { + return Collections.emptyList(); + } + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/GraphQlAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/GraphQlAutoConfiguration.java index 1599bbf89a8..1e5607b4e50 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/GraphQlAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/GraphQlAutoConfiguration.java @@ -48,7 +48,6 @@ import org.springframework.graphql.execution.DataFetcherExceptionResolver; import org.springframework.graphql.execution.DefaultBatchLoaderRegistry; import org.springframework.graphql.execution.ExecutionGraphQlService; import org.springframework.graphql.execution.GraphQlSource; -import org.springframework.graphql.execution.MissingSchemaException; import org.springframework.graphql.execution.RuntimeWiringConfigurer; /** @@ -60,6 +59,7 @@ import org.springframework.graphql.execution.RuntimeWiringConfigurer; */ @AutoConfiguration @ConditionalOnClass({ GraphQL.class, GraphQlSource.class }) +@ConditionalOnGraphQlSchema @EnableConfigurationProperties(GraphQlProperties.class) public class GraphQlAutoConfiguration { @@ -87,12 +87,7 @@ public class GraphQlAutoConfiguration { } wiringConfigurers.orderedStream().forEach(builder::configureRuntimeWiring); sourceCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder)); - try { - return builder.build(); - } - catch (MissingSchemaException ex) { - throw new InvalidSchemaLocationsException(schemaLocations, resourcePatternResolver, ex); - } + return builder.build(); } private Builder enableIntrospection(Builder wiring) { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/InvalidSchemaLocationsException.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/InvalidSchemaLocationsException.java deleted file mode 100644 index 71f15f9861e..00000000000 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/InvalidSchemaLocationsException.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2020-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.autoconfigure.graphql; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import org.springframework.core.NestedRuntimeException; -import org.springframework.core.io.support.ResourcePatternResolver; -import org.springframework.util.Assert; - -/** - * {@link InvalidSchemaLocationsException} thrown when no schema file could be found in - * the provided locations. - * - * @author Brian Clozel - * @since 2.7.0 - */ -public class InvalidSchemaLocationsException extends NestedRuntimeException { - - private final List schemaLocations; - - public InvalidSchemaLocationsException(String[] locations, ResourcePatternResolver resolver) { - this(locations, resolver, null); - } - - public InvalidSchemaLocationsException(String[] locations, ResourcePatternResolver resolver, Throwable cause) { - super("No schema file could be found in the provided locations.", cause); - Assert.notEmpty(locations, "locations should not be empty"); - Assert.notNull(resolver, "resolver should not be null"); - List providedLocations = new ArrayList<>(); - for (String location : locations) { - try { - String uri = resolver.getResource(location).getURI().toASCIIString(); - providedLocations.add(new SchemaLocation(location, uri)); - } - catch (IOException ex) { - providedLocations.add(new SchemaLocation(location, "")); - } - } - this.schemaLocations = Collections.unmodifiableList(providedLocations); - } - - /** - * Return the list of provided locations where to look for schemas. - * @return the list of locations - */ - public List getSchemaLocations() { - return this.schemaLocations; - } - - /** - * The location where to look for schemas. - */ - public static class SchemaLocation { - - private final String location; - - private final String uri; - - SchemaLocation(String location, String uri) { - this.location = location; - this.uri = uri; - } - - /** - * Return the location String to be resolved by a {@link ResourcePatternResolver}. - * @return the location - */ - public String getLocation() { - return this.location; - } - - /** - * Return the resolved URI String for this location, an empty String if resolution - * failed. - * @return the resolved location or an empty String - */ - public String getUri() { - return this.uri; - } - - } - -} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/InvalidSchemaLocationsExceptionFailureAnalyzer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/InvalidSchemaLocationsExceptionFailureAnalyzer.java deleted file mode 100644 index 66b8aa34bfe..00000000000 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/InvalidSchemaLocationsExceptionFailureAnalyzer.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2012-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.autoconfigure.graphql; - -import org.springframework.boot.diagnostics.AbstractFailureAnalyzer; -import org.springframework.boot.diagnostics.FailureAnalysis; - -/** - * An implementation of {@link AbstractFailureAnalyzer} to analyze failures caused by - * {@link InvalidSchemaLocationsException}. - * - * @author Brian Clozel - */ -class InvalidSchemaLocationsExceptionFailureAnalyzer extends AbstractFailureAnalyzer { - - @Override - protected FailureAnalysis analyze(Throwable rootFailure, InvalidSchemaLocationsException cause) { - String message = "Could not find any GraphQL schema file under configured locations."; - StringBuilder action = new StringBuilder( - "Check that the following locations contain schema files: " + System.lineSeparator()); - for (InvalidSchemaLocationsException.SchemaLocation schemaLocation : cause.getSchemaLocations()) { - action.append(String.format("- '%s' (%s)" + System.lineSeparator(), schemaLocation.getUri(), - schemaLocation.getLocation())); - } - return new FailureAnalysis(message, action.toString(), cause); - } - -} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories index 290bdfaa0ff..29878795695 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories @@ -26,7 +26,6 @@ org.springframework.boot.diagnostics.FailureAnalyzer=\ org.springframework.boot.autoconfigure.data.redis.RedisUrlSyntaxFailureAnalyzer,\ org.springframework.boot.autoconfigure.diagnostics.analyzer.NoSuchBeanDefinitionFailureAnalyzer,\ org.springframework.boot.autoconfigure.flyway.FlywayMigrationScriptMissingFailureAnalyzer,\ -org.springframework.boot.autoconfigure.graphql.InvalidSchemaLocationsExceptionFailureAnalyzer,\ org.springframework.boot.autoconfigure.jdbc.DataSourceBeanCreationFailureAnalyzer,\ org.springframework.boot.autoconfigure.jdbc.HikariDriverConfigurationFailureAnalyzer,\ org.springframework.boot.autoconfigure.jooq.NoDslContextBeanFailureAnalyzer,\ diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/DefaultGraphQlSchemaConditionTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/DefaultGraphQlSchemaConditionTests.java new file mode 100644 index 00000000000..cd8391f723f --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/DefaultGraphQlSchemaConditionTests.java @@ -0,0 +1,105 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.graphql; + +import java.util.Collection; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport; +import org.springframework.boot.test.context.assertj.AssertableApplicationContext; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ConditionalOnGraphQlSchema}. + * + * @author Brian Clozel + */ +class DefaultGraphQlSchemaConditionTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner(); + + @Test + void matchesWhenSchemaFilesAreDetected() { + this.contextRunner.withUserConfiguration(TestingConfiguration.class).run((context) -> { + didMatch(context); + assertThat(conditionReportMessage(context)).contains("@ConditionalOnGraphQlSchema found schemas") + .contains("@ConditionalOnGraphQlSchema did not find GraphQlSourceBuilderCustomizer"); + }); + } + + @Test + void matchesWhenCustomizerIsDetected() { + this.contextRunner.withUserConfiguration(CustomCustomizerConfiguration.class, TestingConfiguration.class) + .withPropertyValues("spring.graphql.schema.locations=classpath:graphql/missing").run((context) -> { + didMatch(context); + assertThat(conditionReportMessage(context)).contains( + "@ConditionalOnGraphQlSchema did not find schema files in locations 'classpath:graphql/missing/'") + .contains("@ConditionalOnGraphQlSchema found customizer myBuilderCuystomizer"); + }); + } + + @Test + void doesNotMatchWhenBothAreMissing() { + this.contextRunner.withUserConfiguration(TestingConfiguration.class) + .withPropertyValues("spring.graphql.schema.locations=classpath:graphql/missing").run((context) -> { + assertThat(context).doesNotHaveBean("success"); + assertThat(conditionReportMessage(context)).contains( + "@ConditionalOnGraphQlSchema did not find schema files in locations 'classpath:graphql/missing/'") + .contains("@ConditionalOnGraphQlSchema did not find GraphQlSourceBuilderCustomizer"); + }); + } + + private void didMatch(AssertableApplicationContext context) { + assertThat(context).hasBean("success"); + assertThat(context.getBean("success")).isEqualTo("success"); + } + + private String conditionReportMessage(AssertableApplicationContext context) { + Collection conditionAndOutcomes = ConditionEvaluationReport + .get(context.getSourceApplicationContext().getBeanFactory()).getConditionAndOutcomesBySource().values(); + return conditionAndOutcomes.iterator().next().iterator().next().getOutcome().getMessage(); + } + + @Configuration(proxyBeanMethods = false) + @ConditionalOnGraphQlSchema + static class TestingConfiguration { + + @Bean + String success() { + return "success"; + } + + } + + @Configuration(proxyBeanMethods = false) + static class CustomCustomizerConfiguration { + + @Bean + GraphQlSourceBuilderCustomizer myBuilderCuystomizer() { + return (builder) -> { + + }; + } + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/GraphQlAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/GraphQlAutoConfigurationTests.java index 09350f17efd..9d4d4e4f3f0 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/GraphQlAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/GraphQlAutoConfigurationTests.java @@ -40,7 +40,6 @@ import org.springframework.graphql.execution.BatchLoaderRegistry; import org.springframework.graphql.execution.DataFetcherExceptionResolver; import org.springframework.graphql.execution.DataLoaderRegistrar; import org.springframework.graphql.execution.GraphQlSource; -import org.springframework.graphql.execution.MissingSchemaException; import org.springframework.graphql.execution.RuntimeWiringConfigurer; import static org.assertj.core.api.Assertions.assertThat; @@ -75,11 +74,9 @@ class GraphQlAutoConfigurationTests { } @Test - void shouldFailWhenSchemaFileIsMissing() { - this.contextRunner.withPropertyValues("spring.graphql.schema.locations:classpath:missing/").run((context) -> { - assertThat(context).hasFailed(); - assertThat(context).getFailure().getRootCause().isInstanceOf(MissingSchemaException.class); - }); + void shouldBackoffWhenSchemaFileIsMissing() { + this.contextRunner.withPropertyValues("spring.graphql.schema.locations:classpath:missing/") + .run((context) -> assertThat(context).hasNotFailed().doesNotHaveBean(GraphQlSource.class)); } @Test diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/InvalidSchemaLocationsExceptionFailureAnalyzerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/InvalidSchemaLocationsExceptionFailureAnalyzerTests.java deleted file mode 100644 index 24bc06be73f..00000000000 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/InvalidSchemaLocationsExceptionFailureAnalyzerTests.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2012-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.autoconfigure.graphql; - -import org.junit.jupiter.api.Test; - -import org.springframework.boot.diagnostics.FailureAnalysis; -import org.springframework.core.io.support.PathMatchingResourcePatternResolver; -import org.springframework.core.io.support.ResourcePatternResolver; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link InvalidSchemaLocationsExceptionFailureAnalyzer} - * - * @author Brian Clozel - */ -class InvalidSchemaLocationsExceptionFailureAnalyzerTests { - - private final InvalidSchemaLocationsExceptionFailureAnalyzer analyzer = new InvalidSchemaLocationsExceptionFailureAnalyzer(); - - private final ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); - - private final String[] missingLocation = new String[] { - "classpath:org/springframework/boot/autoconfigure/graphql/missing/" }; - - private final String[] existingLocation = new String[] { - "classpath:org/springframework/boot/autoconfigure/graphql/" }; - - @Test - void shouldReportCause() { - InvalidSchemaLocationsException exception = new InvalidSchemaLocationsException(this.existingLocation, - this.resolver); - FailureAnalysis analysis = this.analyzer.analyze(exception); - assertThat(analysis.getCause()).isInstanceOf(InvalidSchemaLocationsException.class); - assertThat(analysis.getAction()).contains("Check that the following locations contain schema files:"); - } - - @Test - void shouldListUnresolvableLocation() { - InvalidSchemaLocationsException exception = new InvalidSchemaLocationsException(this.missingLocation, - this.resolver); - FailureAnalysis analysis = this.analyzer.analyze(exception); - assertThat(analysis.getAction()).contains(this.existingLocation[0]).doesNotContain("file:"); - } - - @Test - void shouldListExistingLocation() { - InvalidSchemaLocationsException exception = new InvalidSchemaLocationsException(this.existingLocation, - this.resolver); - FailureAnalysis analysis = this.analyzer.analyze(exception); - assertThat(analysis.getAction()).contains(this.existingLocation[0]).contains("file:"); - } - -} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/InvalidSchemaLocationsExceptionTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/InvalidSchemaLocationsExceptionTests.java deleted file mode 100644 index b61e3cb50d4..00000000000 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/InvalidSchemaLocationsExceptionTests.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2012-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.autoconfigure.graphql; - -import org.junit.jupiter.api.Test; - -import org.springframework.core.io.support.PathMatchingResourcePatternResolver; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; - -/** - * Tests for {@link InvalidSchemaLocationsException}. - * - * @author Brian Clozel - */ -class InvalidSchemaLocationsExceptionTests { - - private final String schemaFolder = "graphql/"; - - private final String[] locations = new String[] { "classpath:" + this.schemaFolder }; - - @Test - void shouldRejectEmptyLocations() { - assertThatIllegalArgumentException().isThrownBy( - () -> new InvalidSchemaLocationsException(new String[] {}, new PathMatchingResourcePatternResolver())) - .isInstanceOf(IllegalArgumentException.class).withMessage("locations should not be empty"); - } - - @Test - void shouldRejectNullResolver() { - assertThatIllegalArgumentException().isThrownBy(() -> new InvalidSchemaLocationsException(this.locations, null)) - .isInstanceOf(IllegalArgumentException.class).withMessage("resolver should not be null"); - } - - @Test - void shouldExposeConfiguredLocations() { - InvalidSchemaLocationsException exception = new InvalidSchemaLocationsException(this.locations, - new PathMatchingResourcePatternResolver()); - assertThat(exception.getSchemaLocations()).hasSize(1); - InvalidSchemaLocationsException.SchemaLocation schemaLocation = exception.getSchemaLocations().get(0); - assertThat(schemaLocation.getLocation()).isEqualTo(this.locations[0]); - assertThat(schemaLocation.getUri()).endsWith(this.schemaFolder); - } - - @Test - void shouldNotFailWithUnresolvableLocations() { - String unresolved = "classpath:unresolved/"; - InvalidSchemaLocationsException exception = new InvalidSchemaLocationsException(new String[] { unresolved }, - new PathMatchingResourcePatternResolver()); - assertThat(exception.getSchemaLocations()).hasSize(1); - InvalidSchemaLocationsException.SchemaLocation schemaLocation = exception.getSchemaLocations().get(0); - assertThat(schemaLocation.getLocation()).isEqualTo(unresolved); - assertThat(schemaLocation.getUri()).isEmpty(); - } - -} From 43889104356b374efd7fb64b35fc464103141987 Mon Sep 17 00:00:00 2001 From: Gautham Mohan Date: Mon, 7 Mar 2022 11:55:48 +0530 Subject: [PATCH 5/5] Consider DataFetcherExceptionResolver in GraphQTest Prior to this commit, `@GraphQlTest` slice tests would only consider JsonComponent, RuntimeWiringConfigurer, Converter and GenericConverter beans. But DataFetcherExceptionResolver, Instrumentation and GraphQlSourceBuilderCustomizer are used in setting up the GraphQL source. This commit ensures that the `GraphQlTypeExcludeFilter` considers those bean types. Closes gh-30078 --- .../src/docs/asciidoc/features/testing.adoc | 2 +- .../autoconfigure/graphql/GraphQlTest.java | 7 +- .../graphql/GraphQlTypeExcludeFilter.java | 7 ++ .../GraphQlTypeExcludeFilterTests.java | 93 +++++++++++++++++++ 4 files changed, 107 insertions(+), 2 deletions(-) diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/testing.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/testing.adoc index b160e51cfc7..ce0a737005b 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/testing.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/testing.adoc @@ -444,7 +444,7 @@ There are `GraphQlTester` variants and Spring Boot will auto-configure them depe Spring Boot helps you to test your {spring-graphql-docs}#controllers[Spring GraphQL Controllers] with the `@GraphQlTest` annotation. `@GraphQlTest` auto-configures the Spring GraphQL infrastructure, without any transport nor server being involved. -This limits scanned beans to `@Controller`, `RuntimeWiringConfigurer`, `JsonComponent`, `Converter` and `GenericConverter`. +This limits scanned beans to `@Controller`, `RuntimeWiringConfigurer`, `JsonComponent`, `Converter`, `GenericConverter`, `DataFetcherExceptionResolver`, `Instrumentation` and `GraphQlSourceBuilderCustomizer`. Regular `@Component` and `@ConfigurationProperties` beans are not scanned when the `@GraphQlTest` annotation is used. `@EnableConfigurationProperties` can be used to include `@ConfigurationProperties` beans. diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/graphql/GraphQlTest.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/graphql/GraphQlTest.java index c096f037ba3..1a72744bdc8 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/graphql/GraphQlTest.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/graphql/GraphQlTest.java @@ -51,6 +51,9 @@ import org.springframework.test.context.junit.jupiter.SpringExtension; *
  • {@code @JsonComponent} *
  • {@code Converter} *
  • {@code GenericConverter} + *
  • {@code DataFetcherExceptionResolver} + *
  • {@code Instrumentation} + *
  • {@code GraphQlSourceBuilderCustomizer} * *

    * The annotation does not automatically load {@code @Component}, {@code @Service}, @@ -121,7 +124,9 @@ public @interface GraphQlTest { * {@link SpringBootApplication @SpringBootApplication}. By default, only * {@code @Controller} (when no explicit {@link #controllers() controllers} are * defined), {@code RuntimeWiringConfigurer}, {@code @JsonComponent}, - * {@code Converter}, and {@code GenericConverter} beans are included. + * {@code Converter}, {@code GenericConverter}, {@code DataFetcherExceptionResolver}, + * {@code Instrumentation} and {@code GraphQlSourceBuilderCustomizer} beans are + * included. * @see #includeFilters() * @see #excludeFilters() * @return if default filters should be used diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/graphql/GraphQlTypeExcludeFilter.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/graphql/GraphQlTypeExcludeFilter.java index 1ae5b52f603..861650de745 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/graphql/GraphQlTypeExcludeFilter.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/graphql/GraphQlTypeExcludeFilter.java @@ -21,11 +21,15 @@ import java.util.Collections; import java.util.LinkedHashSet; import java.util.Set; +import graphql.execution.instrumentation.Instrumentation; + +import org.springframework.boot.autoconfigure.graphql.GraphQlSourceBuilderCustomizer; import org.springframework.boot.context.TypeExcludeFilter; import org.springframework.boot.jackson.JsonComponent; import org.springframework.boot.test.autoconfigure.filter.StandardAnnotationCustomizableTypeExcludeFilter; import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.GenericConverter; +import org.springframework.graphql.execution.DataFetcherExceptionResolver; import org.springframework.graphql.execution.RuntimeWiringConfigurer; import org.springframework.stereotype.Controller; import org.springframework.util.ClassUtils; @@ -51,6 +55,9 @@ public class GraphQlTypeExcludeFilter extends StandardAnnotationCustomizableType includes.add(RuntimeWiringConfigurer.class); includes.add(Converter.class); includes.add(GenericConverter.class); + includes.add(DataFetcherExceptionResolver.class); + includes.add(Instrumentation.class); + includes.add(GraphQlSourceBuilderCustomizer.class); for (String optionalInclude : OPTIONAL_INCLUDES) { try { includes.add(ClassUtils.forName(optionalInclude, null)); diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/graphql/GraphQlTypeExcludeFilterTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/graphql/GraphQlTypeExcludeFilterTests.java index 53fa09adcbd..098a3a765bb 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/graphql/GraphQlTypeExcludeFilterTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/graphql/GraphQlTypeExcludeFilterTests.java @@ -17,17 +17,35 @@ package org.springframework.boot.test.autoconfigure.graphql; import java.io.IOException; +import java.util.List; import com.fasterxml.jackson.databind.module.SimpleModule; +import graphql.ExecutionResult; +import graphql.GraphQLError; +import graphql.execution.instrumentation.ExecutionStrategyInstrumentationContext; +import graphql.execution.instrumentation.Instrumentation; +import graphql.execution.instrumentation.InstrumentationContext; +import graphql.execution.instrumentation.parameters.InstrumentationExecuteOperationParameters; +import graphql.execution.instrumentation.parameters.InstrumentationExecutionParameters; +import graphql.execution.instrumentation.parameters.InstrumentationExecutionStrategyParameters; +import graphql.execution.instrumentation.parameters.InstrumentationFieldFetchParameters; +import graphql.execution.instrumentation.parameters.InstrumentationFieldParameters; +import graphql.execution.instrumentation.parameters.InstrumentationValidationParameters; +import graphql.language.Document; +import graphql.schema.DataFetchingEnvironment; import graphql.schema.idl.RuntimeWiring; +import graphql.validation.ValidationError; import org.junit.jupiter.api.Test; import reactor.core.publisher.Mono; +import org.springframework.boot.autoconfigure.graphql.GraphQlSourceBuilderCustomizer; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.FilterType; import org.springframework.core.type.classreading.MetadataReader; import org.springframework.core.type.classreading.MetadataReaderFactory; import org.springframework.core.type.classreading.SimpleMetadataReaderFactory; +import org.springframework.graphql.execution.DataFetcherExceptionResolver; +import org.springframework.graphql.execution.GraphQlSource.Builder; import org.springframework.graphql.execution.RuntimeWiringConfigurer; import org.springframework.graphql.web.WebInput; import org.springframework.graphql.web.WebInterceptor; @@ -58,6 +76,9 @@ class GraphQlTypeExcludeFilterTests { assertThat(excludes(filter, ExampleRepository.class)).isTrue(); assertThat(excludes(filter, ExampleWebInterceptor.class)).isTrue(); assertThat(excludes(filter, ExampleModule.class)).isFalse(); + assertThat(excludes(filter, ExampleDataFetcherExceptionResolver.class)).isFalse(); + assertThat(excludes(filter, ExampleInstrumentation.class)).isFalse(); + assertThat(excludes(filter, ExampleGraphQlSourceBuilderCustomizer.class)).isFalse(); } @Test @@ -70,6 +91,9 @@ class GraphQlTypeExcludeFilterTests { assertThat(excludes(filter, ExampleRepository.class)).isTrue(); assertThat(excludes(filter, ExampleWebInterceptor.class)).isTrue(); assertThat(excludes(filter, ExampleModule.class)).isFalse(); + assertThat(excludes(filter, ExampleDataFetcherExceptionResolver.class)).isFalse(); + assertThat(excludes(filter, ExampleInstrumentation.class)).isFalse(); + assertThat(excludes(filter, ExampleGraphQlSourceBuilderCustomizer.class)).isFalse(); } @Test @@ -82,6 +106,9 @@ class GraphQlTypeExcludeFilterTests { assertThat(excludes(filter, ExampleRepository.class)).isTrue(); assertThat(excludes(filter, ExampleWebInterceptor.class)).isTrue(); assertThat(excludes(filter, ExampleModule.class)).isTrue(); + assertThat(excludes(filter, ExampleDataFetcherExceptionResolver.class)).isTrue(); + assertThat(excludes(filter, ExampleInstrumentation.class)).isTrue(); + assertThat(excludes(filter, ExampleGraphQlSourceBuilderCustomizer.class)).isTrue(); } @Test @@ -94,6 +121,9 @@ class GraphQlTypeExcludeFilterTests { assertThat(excludes(filter, ExampleRepository.class)).isFalse(); assertThat(excludes(filter, ExampleWebInterceptor.class)).isTrue(); assertThat(excludes(filter, ExampleModule.class)).isFalse(); + assertThat(excludes(filter, ExampleDataFetcherExceptionResolver.class)).isFalse(); + assertThat(excludes(filter, ExampleInstrumentation.class)).isFalse(); + assertThat(excludes(filter, ExampleGraphQlSourceBuilderCustomizer.class)).isFalse(); } @Test @@ -106,6 +136,9 @@ class GraphQlTypeExcludeFilterTests { assertThat(excludes(filter, ExampleRepository.class)).isTrue(); assertThat(excludes(filter, ExampleWebInterceptor.class)).isTrue(); assertThat(excludes(filter, ExampleModule.class)).isFalse(); + assertThat(excludes(filter, ExampleDataFetcherExceptionResolver.class)).isFalse(); + assertThat(excludes(filter, ExampleInstrumentation.class)).isFalse(); + assertThat(excludes(filter, ExampleGraphQlSourceBuilderCustomizer.class)).isFalse(); } private boolean excludes(GraphQlTypeExcludeFilter filter, Class type) throws IOException { @@ -181,4 +214,64 @@ class GraphQlTypeExcludeFilterTests { } + static class ExampleDataFetcherExceptionResolver implements DataFetcherExceptionResolver { + + @Override + public Mono> resolveException(Throwable exception, DataFetchingEnvironment environment) { + return null; + } + + } + + static class ExampleInstrumentation implements Instrumentation { + + @Override + public InstrumentationContext beginExecution(InstrumentationExecutionParameters parameters) { + return null; + } + + @Override + public InstrumentationContext beginParse(InstrumentationExecutionParameters parameters) { + return null; + } + + @Override + public InstrumentationContext> beginValidation( + InstrumentationValidationParameters parameters) { + return null; + } + + @Override + public InstrumentationContext beginExecuteOperation( + InstrumentationExecuteOperationParameters parameters) { + return null; + } + + @Override + public ExecutionStrategyInstrumentationContext beginExecutionStrategy( + InstrumentationExecutionStrategyParameters parameters) { + return null; + } + + @Override + public InstrumentationContext beginField(InstrumentationFieldParameters parameters) { + return null; + } + + @Override + public InstrumentationContext beginFieldFetch(InstrumentationFieldFetchParameters parameters) { + return null; + } + + } + + static class ExampleGraphQlSourceBuilderCustomizer implements GraphQlSourceBuilderCustomizer { + + @Override + public void customize(Builder builder) { + + } + + } + }