spring-boot icon indicating copy to clipboard operation
spring-boot copied to clipboard

GraphQL schema files are not found when a root containing a graphql package appears on the classpath before the root that contains the schema files

Open estigma88 opened this issue 2 years ago • 5 comments

Version Spring Boot 2.7.1

Description We created a new itest source set in Gradle and when we run the test using @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT), seems like the autoconfigure GraphQL components are not provisioned.

How to reproduce it This project contains a pretty simple GraphQL controller, with two tests:

  • One inside the test source set, using RestAssured to call the GraphQL endpoint, which works fine (HTTP 200)
  • One inside the itest source set, using RestAssured to call the GraphQL endpoint, which doesn't work (HTTP 404)

Clues I did some debugging and found that ConditionalOnGraphQlSchema returns true for the test source set, and false for the itest source set, it seems like this line on DefaultGraphQlSchemaCondition cannot find the GraphQL schema, and therefore, Spring Boot doesn't contribute the Graphql autoconfiguration.

My guess is this issue might be for Spring Boot and not Gradle configuration, as other resources work fine, like liquibase changelogs and application.yml files.

estigma88 avatar Jul 16 '22 00:07 estigma88

It's a classpath ordering problem. Your itest task has the project's main resources on its classpath after all of its dependencies whereas the test task has the project's main resources on its classpath before all of its dependencies. This ordering matters because the graphql-java jar contains a graphql package. When it's on the classpath before the project's main resources, this package prevents the graphql/schema.graphqls file from being found as it ends up looking inside graphql-java for it as that's the first occurrence of graphql/ on the classpath.

You can fix the problem by creating your itest source set like this:

sourceSets {
	create("itest") {
		compileClasspath = sourceSets.main.get().output + compileClasspath
		runtimeClasspath = sourceSets.main.get().output + runtimeClasspath
	}
}

I'm going to leave this issue open as I think we should consider a different default schema location. "classpath*:graphql/**/" would be more robust, although it may be quite slow depending on the size of the classpath.

What do you think, @bclozel and @rstoyanchev?

wilkinsona avatar Jul 19 '22 18:07 wilkinsona

This might be related to https://github.com/spring-projects/spring-graphql/issues/308#issuecomment-1058141073

bclozel avatar Jul 19 '22 18:07 bclozel

Thanks for the link, Brian. That's the same problem indeed, just with a different source of the unwanted graphql package that stops the file from being found.

wilkinsona avatar Jul 19 '22 18:07 wilkinsona

That's the same problem indeed, just with a different source of the unwanted graphql package that stops the file from being found.

As of https://github.com/spring-projects/spring-graphql/issues/338, we look in different places for schema vs test request vs client request files, which eliminates a very basic reason to shadowing graphql/ under main and test sources that most apps would run into otherwise.

For the rest, using classpath*: by default will find all possible candidates out of the box, including some that may not be intended for inclusion, while classpath: requires an explicit action to decide how to deal with such shadowing. I tend to think the more explicit opt-in option is preferable. I've already made an adjustment to the Spring for GraphQL docs to make that easier to spot.

rstoyanchev avatar Sep 20 '22 13:09 rstoyanchev

Thanks Rossen, I'll add a similar note in the Spring Boot reference docs, I'll turn this into a documentation issue then.

bclozel avatar Sep 20 '22 13:09 bclozel