From 998a2fa7b4a4d4b149ac16fa301857914fe54e6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Nicoll?= Date: Thu, 16 Oct 2025 16:30:23 +0200 Subject: [PATCH] Fix configuration of HttpGraphQlTester with a running server This commit restores the user of HttpGraphQlTester when it is configured against a running server. The logic that appends the graphQl path to the HTTP url was lost while refactoring the HTTP clients infrastructure. To work against the new API, BaseUrl has been updated to provide the ability to append a path to its URL Closes gh-47659 --- .../boot/test/http/server/BaseUrl.java | 47 +++++-- .../boot/test/http/server/BaseUrlTests.java | 18 +++ .../HttpGraphQlTesterAutoConfiguration.java | 30 ++++- ...tpGraphQlTesterAutoConfigurationTests.java | 115 ++++++++++++++++++ .../SampleGraphQlApplicationTests.java | 56 +++++++++ 5 files changed, 250 insertions(+), 16 deletions(-) create mode 100644 module/spring-boot-graphql-test/src/test/java/org/springframework/boot/graphql/test/autoconfigure/tester/HttpGraphQlTesterAutoConfigurationTests.java create mode 100644 smoke-test/spring-boot-smoke-test-graphql/src/test/java/smoketest/graphql/SampleGraphQlApplicationTests.java diff --git a/core/spring-boot-test/src/main/java/org/springframework/boot/test/http/server/BaseUrl.java b/core/spring-boot-test/src/main/java/org/springframework/boot/test/http/server/BaseUrl.java index 275a41e046c..97b9d9832c6 100644 --- a/core/spring-boot-test/src/main/java/org/springframework/boot/test/http/server/BaseUrl.java +++ b/core/spring-boot-test/src/main/java/org/springframework/boot/test/http/server/BaseUrl.java @@ -27,6 +27,7 @@ import org.springframework.util.StringUtils; * A base URL that can be used to connect to the running server. * * @author Phillip Webb + * @author Stephane Nicoll * @since 4.0.0 */ public interface BaseUrl { @@ -67,6 +68,13 @@ public interface BaseUrl { */ String resolve(); + /** + * Return a new instance that applies the given {@code path}. + * @param path a path to append + * @return a new instance with the path added + */ + BaseUrl withPath(String path); + /** * Factory method to create a new {@link BaseUrl}. * @param url the URL to use @@ -84,20 +92,37 @@ public interface BaseUrl { * @return a new {@link BaseUrl} instance */ static BaseUrl of(boolean https, Supplier resolver) { - Assert.notNull(resolver, "'resolver' must not be null"); - return new BaseUrl() { + return new DefaultBaseUrl(https, resolver); + } - @Override - public boolean isHttps() { - return https; - } + final class DefaultBaseUrl implements BaseUrl { - @Override - public String resolve() { - return resolver.get(); - } + private final boolean https; + + private final Supplier resolver; + + private DefaultBaseUrl(boolean https, Supplier resolver) { + Assert.notNull(resolver, "'resolver' must not be null"); + this.https = https; + this.resolver = resolver; + } + + @Override + public boolean isHttps() { + return this.https; + } + + @Override + public String resolve() { + return this.resolver.get(); + } + + @Override + public BaseUrl withPath(String path) { + Supplier updatedResolver = () -> this.resolver.get() + path; + return new DefaultBaseUrl(this.https, updatedResolver); + } - }; } } diff --git a/core/spring-boot-test/src/test/java/org/springframework/boot/test/http/server/BaseUrlTests.java b/core/spring-boot-test/src/test/java/org/springframework/boot/test/http/server/BaseUrlTests.java index a83eff370ed..25e86ad9b67 100644 --- a/core/spring-boot-test/src/test/java/org/springframework/boot/test/http/server/BaseUrlTests.java +++ b/core/spring-boot-test/src/test/java/org/springframework/boot/test/http/server/BaseUrlTests.java @@ -84,4 +84,22 @@ class BaseUrlTests { .withMessage("'resolver' must not be null"); } + @Test + void withPath() { + BaseUrl baseUrl = BaseUrl.of("http://localhost"); + assertThat(baseUrl.withPath("/context").resolve("")).isEqualTo("http://localhost/context"); + assertThat(baseUrl.withPath("/context").withPath("/test").resolve("path")) + .isEqualTo("http://localhost/context/test/path"); + } + + @Test + void withPathInvokesParentResolver() { + AtomicInteger atomicInteger = new AtomicInteger(); + BaseUrl baseUrl = BaseUrl.of(true, + () -> "https://example.com/" + atomicInteger.incrementAndGet()); + assertThat(baseUrl.withPath("/context").resolve("")).isEqualTo("https://example.com/1/context"); + assertThat(baseUrl.withPath("/context").withPath("/test").resolve("path")) + .isEqualTo("https://example.com/2/context/test/path"); + } + } diff --git a/module/spring-boot-graphql-test/src/main/java/org/springframework/boot/graphql/test/autoconfigure/tester/HttpGraphQlTesterAutoConfiguration.java b/module/spring-boot-graphql-test/src/main/java/org/springframework/boot/graphql/test/autoconfigure/tester/HttpGraphQlTesterAutoConfiguration.java index 544a6e0810b..f5095506679 100644 --- a/module/spring-boot-graphql-test/src/main/java/org/springframework/boot/graphql/test/autoconfigure/tester/HttpGraphQlTesterAutoConfiguration.java +++ b/module/spring-boot-graphql-test/src/main/java/org/springframework/boot/graphql/test/autoconfigure/tester/HttpGraphQlTesterAutoConfiguration.java @@ -16,11 +16,18 @@ package org.springframework.boot.graphql.test.autoconfigure.tester; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.graphql.autoconfigure.GraphQlProperties; +import org.springframework.boot.test.http.client.BaseUrlUriBuilderFactory; +import org.springframework.boot.test.http.server.BaseUrl; +import org.springframework.boot.test.http.server.BaseUrlProviders; +import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.graphql.test.tester.HttpGraphQlTester; import org.springframework.graphql.test.tester.WebGraphQlTester; @@ -31,19 +38,32 @@ import org.springframework.web.reactive.function.client.WebClient; * Auto-configuration for {@link HttpGraphQlTester}. * * @author Brian Clozel + * @author Stephane Nicoll * @since 4.0.0 */ -@AutoConfiguration(afterName = { "org.springframework.boot.webtestclient.WebTestClientAutoConfiguration", - "org.springframework.boot.webmvc.test.autoconfigure.MockMvcAutoConfiguration" }) +@AutoConfiguration(afterName = "org.springframework.boot.webtestclient.WebTestClientAutoConfiguration") @ConditionalOnClass({ WebClient.class, WebTestClient.class, WebGraphQlTester.class }) +@EnableConfigurationProperties(GraphQlProperties.class) public final class HttpGraphQlTesterAutoConfiguration { @Bean @ConditionalOnBean(WebTestClient.class) @ConditionalOnMissingBean - HttpGraphQlTester webTestClientGraphQlTester(WebTestClient webTestClient, GraphQlProperties properties) { - WebTestClient mutatedWebTestClient = webTestClient.mutate().baseUrl(properties.getHttp().getPath()).build(); - return HttpGraphQlTester.create(mutatedWebTestClient); + HttpGraphQlTester webTestClientGraphQlTester(ApplicationContext applicationContext, WebTestClient webTestClient, + GraphQlProperties properties) { + String graphQlPath = properties.getHttp().getPath(); + BaseUrl baseUrl = new BaseUrlProviders(applicationContext).getBaseUrl(); + WebTestClient graphQlWebTestClient = configureGraphQlWebTestClient(webTestClient, baseUrl, graphQlPath); + return HttpGraphQlTester.create(graphQlWebTestClient); + } + + private WebTestClient configureGraphQlWebTestClient(WebTestClient webTestClient, @Nullable BaseUrl baseUrl, + String graphQlPath) { + WebTestClient.Builder builder = webTestClient.mutate(); + if (baseUrl != null) { + return builder.uriBuilderFactory(BaseUrlUriBuilderFactory.get(baseUrl.withPath(graphQlPath))).build(); + } + return builder.baseUrl(graphQlPath).build(); } } diff --git a/module/spring-boot-graphql-test/src/test/java/org/springframework/boot/graphql/test/autoconfigure/tester/HttpGraphQlTesterAutoConfigurationTests.java b/module/spring-boot-graphql-test/src/test/java/org/springframework/boot/graphql/test/autoconfigure/tester/HttpGraphQlTesterAutoConfigurationTests.java new file mode 100644 index 00000000000..7504e5de0ba --- /dev/null +++ b/module/spring-boot-graphql-test/src/test/java/org/springframework/boot/graphql/test/autoconfigure/tester/HttpGraphQlTesterAutoConfigurationTests.java @@ -0,0 +1,115 @@ +/* + * Copyright 2012-present 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.graphql.test.autoconfigure.tester; + +import java.net.URI; + +import org.assertj.core.api.InstanceOfAssertFactories; +import org.jspecify.annotations.Nullable; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.boot.test.http.server.BaseUrl; +import org.springframework.boot.test.http.server.BaseUrlProvider; +import org.springframework.boot.testsupport.classpath.resources.WithResource; +import org.springframework.boot.webtestclient.WebTestClientAutoConfiguration; +import org.springframework.graphql.test.tester.HttpGraphQlTester; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.web.util.UriBuilderFactory; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link HttpGraphQlTesterAutoConfiguration}. + * + * @author Stephane Nicoll + */ +class HttpGraphQlTesterAutoConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(HttpGraphQlTesterAutoConfiguration.class)); + + @Test + void shouldNotContributeTesterIfWebTestClientNotPresent() { + this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(HttpGraphQlTester.class)); + } + + @Test + void shouldContributeTesterBoutToMockMvc() { + this.contextRunner.withBean(MockMvc.class, () -> mock(MockMvc.class)) + .withConfiguration(AutoConfigurations.of(WebTestClientAutoConfiguration.class)) + .run((context) -> { + assertThat(context).hasSingleBean(HttpGraphQlTester.class); + assertThat(context.getBean(HttpGraphQlTester.class)).extracting("webTestClient") + .extracting("builder") + .extracting("baseUrl") + .isEqualTo("/graphql"); + }); + } + + @Test + @WithResource(name = "META-INF/spring.factories", + content = """ + org.springframework.boot.test.http.server.BaseUrlProvider=\ + org.springframework.boot.graphql.test.autoconfigure.tester.HttpGraphQlTesterAutoConfigurationTests$TestBaseUrlProvider + """) + void shouldContributeTesterBoundToHttpServer() { + this.contextRunner.withConfiguration(AutoConfigurations.of(WebTestClientAutoConfiguration.class)) + .run((context) -> { + assertThat(context).hasSingleBean(HttpGraphQlTester.class); + assertThat(context.getBean(HttpGraphQlTester.class)).extracting("webTestClient") + .extracting("builder") + .extracting("uriBuilderFactory") + .asInstanceOf(InstanceOfAssertFactories.type(UriBuilderFactory.class)) + .satisfies((uriBuilderFactory) -> assertThat(uriBuilderFactory.uriString("/something").build()) + .isEqualTo(URI.create("https://localhost:4242/graphql/something"))); + }); + } + + @Test + @WithResource(name = "META-INF/spring.factories", + content = """ + org.springframework.boot.test.http.server.BaseUrlProvider=\ + org.springframework.boot.graphql.test.autoconfigure.tester.HttpGraphQlTesterAutoConfigurationTests$TestBaseUrlProvider + """) + void shouldContributeTesterBoundToHttpServerUsingCustomGraphQlHttpPath() { + this.contextRunner.withConfiguration(AutoConfigurations.of(WebTestClientAutoConfiguration.class)) + .withPropertyValues("spring.graphql.http.path=/api/graphql") + .run((context) -> { + assertThat(context).hasSingleBean(HttpGraphQlTester.class); + assertThat(context.getBean(HttpGraphQlTester.class)).extracting("webTestClient") + .extracting("builder") + .extracting("uriBuilderFactory") + .asInstanceOf(InstanceOfAssertFactories.type(UriBuilderFactory.class)) + .satisfies((uriBuilderFactory) -> assertThat(uriBuilderFactory.uriString("/something").build()) + .isEqualTo(URI.create("https://localhost:4242/api/graphql/something"))); + }); + } + + @SuppressWarnings("unused") + static class TestBaseUrlProvider implements BaseUrlProvider { + + @Override + public @Nullable BaseUrl getBaseUrl() { + return BaseUrl.of("https://localhost:4242"); + } + + } + +} diff --git a/smoke-test/spring-boot-smoke-test-graphql/src/test/java/smoketest/graphql/SampleGraphQlApplicationTests.java b/smoke-test/spring-boot-smoke-test-graphql/src/test/java/smoketest/graphql/SampleGraphQlApplicationTests.java new file mode 100644 index 00000000000..b69696eba3e --- /dev/null +++ b/smoke-test/spring-boot-smoke-test-graphql/src/test/java/smoketest/graphql/SampleGraphQlApplicationTests.java @@ -0,0 +1,56 @@ +/* + * Copyright 2012-present 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 smoketest.graphql; + +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.graphql.test.autoconfigure.tester.AutoConfigureHttpGraphQlTester; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.graphql.test.tester.HttpGraphQlTester; + +/** + * Tests for {@link SampleGraphQlApplication}. + * + * @author Stephane Nicoll + */ +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@AutoConfigureHttpGraphQlTester +class SampleGraphQlApplicationTests { + + @Autowired + private HttpGraphQlTester graphQlTester; + + @Test + void shouldFindSpringGraphQl() { + this.graphQlTester.document("{ project(slug: \"spring-graphql\") { name } }") + .execute() + .path("project.name") + .entity(String.class) + .isEqualTo("Spring GraphQL"); + } + + @Test + void shouldNotFindUnknownProject() { + this.graphQlTester.document("{ project(slug: \"spring-unknown\") { name } }") + .execute() + .path("project.name") + .pathDoesNotExist(); + } + +}