Skip to content

Query every field of every test schema - #879

Merged
oryan-block merged 14 commits into
masterfrom
test/every-field-resolves
Oct 18, 2026
Merged

oryan-block merged 14 commits into
masterfrom
test/every-field-resolves

Conversation

@oryan-block

@oryan-block oryan-block commented Oct 18, 2026 •

Copy link
Copy Markdown
Collaborator

Checklist

  • Pull requests follows the contribution guide
  • New or modified functionality is covered by tests

Description

A schema that builds can still fail as soon as a field is queried. In #203 a resolver declared as Future<Organization> returned RxJava's Single#toFuture(). The build was fine since the scanner unwraps Future, but graphql-java only waits on a CompletionStage, so the child fields failed with Expected source object to be an instance of '...Organization'. Our tests only run the fields someone wrote a query for, so nothing caught it.

assertEveryFieldResolves() (new, in EveryFieldResolves.kt) is an extension on GraphQLSchema that returns the schema, so it goes at the end of the builder chain, right after makeExecutableSchema(). It runs one operation per root field that selects every field reachable from it, and fails on any GraphQL error:

  • a fragment for every union member and interface implementation, a type repeats at most once on a path, max depth 6
  • arguments without a default get made-up values ("a", "1" for ID, 1, 1.5, true, the first enum value, input objects filled the same way, one field for @oneOf)
  • a subscription has to send an event within 5s, and that event's errors count too
  • except takes schema coordinates (Type.field or Type.field(arg:)) for fields that fail on purpose, or arguments whose made-up value doesn't fit, like a UUID ID or a relay cursor
  • a trailing lambda adds to the ExecutionInput, e.g. a context or a data loader registry

It's called after 163 schema builds in 36 test files. Schemas built once per class get a single every field resolves test instead (8 of them), and builds inside withWarnings { } call it after the block, so its queries can't add to the warnings. The builds left without a check either throw, or fail on every field on purpose (allowUnimplementedResolvers in the description tests, the Spring AOP proxy test, the misspelled input class tests, the rethrow test, futures graphql-java can't wait on).

Fixtures that couldn't be queried are fixed without changing what their tests assert: resolvers returning null for non-null fields, private or inner classes Jackson can't construct, input classes missing a schema field, non-null Kotlin parameters for nullable arguments. Two worth a look:

  • DeprecatedDirectiveTest used users(env: DataFetchingEnvironment) for both users(first: Int) and users(connectionInput: ConnectionInput). The argument got mapped onto env, so users failed on every query. It now has one resolver per argument shape.
  • The custom generic wrapper test in SchemaParserTest used the plain GenericWrapper(CustomGenericWrapper::class, 1) constructor, and its resolvers returned null. With a real value, the wrapper reached graphql-java as is (Expected source object to be an instance of '...Obj' but instead got '...CustomGenericWrapper'), since that constructor passes the value through. It uses withTransformer now. The scanner only reads the type and index, so the scanning part is the same.

The Kotlin test-compile execution moves to process-test-sources (like the main one at process-sources), so the Kotlin test classes exist before javac compiles the Java tests. ResolverMethodsTest.java needs that to call the helper.

It found two library bugs I didn't fix here:

  1. A resolver method inherited from a generic class, with the class's type variable as its parameter (InputResolver<I>.generic(input: I) with GreetingQuery : InputResolver<GreetingInput>()), builds but fails on every query with TypeVariableImpl cannot be cast to class java.lang.Class. The data fetcher uses the unresolved I from Method#getGenericParameterTypes. Reproduced through the public API on graphql-java 26.1 and 25.1. Filed as Resolver parameter typed by the resolver's own type variable fails on every query #880, and Query.generic is excluded in UnmappedInputFieldsTest until it's fixed.
  2. A field with an argument whose resolver only takes a DataFetchingEnvironment builds without a warning, with the argument mapped onto that parameter. The field then fails on every query (the DeprecatedDirectiveTest case above). Same family as Last argument to field resolver is not checked when using the context class schema parser option #418, so I added it there.

Limits: it only runs what the test resolvers return, so a union member a resolver never returns, or the fields under a null, aren't run. Fields with a required custom scalar argument are skipped, since there's no value to make up.

Test only, no library changes. mvn clean verify passes on JDK 25 and JDK 11, and against graphql-java 25.1.

@sonarqubecloud

Copy link
Copy Markdown

@oryan-block
oryan-block merged commit 9a8908a into master Oct 18, 2026
7 checks passed
@oryan-block
oryan-block deleted the test/every-field-resolves branch October 18, 2026 23:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant