# Write GraphQL API Tests for a Spring Boot App

Yes. An agent tests every GraphQL query and mutation in your Spring Boot app, including auth and validation errors, on a real database and opens a pull request.

[Run this on Rush](https://cloud.getrush.ai/run/write-graphql-tests/spring-boot/), in a Project that has your repository.

## What the agent does

The agent clones the repository into its own cloud sandbox and starts from the schema. In gothinkster/spring-boot-realworld-example-app (Spring Boot 2.6.3, Java 11, MyBatis, Netflix DGS 4.9.21 with the DGS codegen plugin), `src/main/resources/schema/schema.graphqls` defines six queries (`article`, `articles`, `me`, `feed`, `profile`, `tags`) and twelve mutations (`createUser`, `login`, `updateUser`, `followUser`, `unfollowUser`, `createArticle`, `updateArticle`, `favoriteArticle`, `unfavoriteArticle`, `deleteArticle`, `addComment`, `deleteComment`). The resolvers live in `src/main/java/io/spring/graphql/`, one `*Datafetcher` or `*Mutation` class per area. The existing tests under `src/test/java/io/spring/` cover the REST controllers with REST Assured and the MyBatis repositories, and none of them sends a GraphQL request.

Then it traces how errors leave the GraphQL layer, because that decides what each error test asserts. `GraphQLCustomizeExceptionHandler` turns `InvalidAuthenticationException` (a bad login) into a DGS error with type `UNAUTHENTICATED`, and a `ConstraintViolationException` into a bad-request error whose extensions list each invalid field. `createUser` is different: it returns the `UserResult` union, so a validation failure comes back as data, an `Error` with `errors` items, not as a GraphQL error. Mutations that need a signed-in user call `SecurityUtil.getCurrentUser().orElseThrow(AuthenticationException::new)`, and that `AuthenticationException` is not one the handler maps, so it falls through to the default handler. The agent writes tests that pin what an unauthenticated call returns today and flags the difference from `login`'s `UNAUTHENTICATED` in the pull request.

It writes the tests as `@SpringBootTest` classes that run each operation through `DgsQueryExecutor`, the DGS test entry point, so the real schema, resolvers, exception handler and MyBatis mappers all run. The database is real too: `application-test.properties` sets `spring.datasource.url=jdbc:sqlite::memory:`, and Flyway applies `db/migration/V1__create_tables.sql` on start, so with `@ActiveProfiles("test")` each test class gets a fresh SQLite schema. `DgsQueryExecutor` does not pass through the servlet filter chain, so authenticated calls set the security context to a real user the test created, the same `UsernamePasswordAuthenticationToken` `JwtTokenFilter` builds; a few tests per area also go through MockMvc with an `Authorization` header carrying a token from `JwtService`, so the filter itself is covered. For each operation there is a happy path (create a user, log in, write an article, follow, favourite, comment), a validation failure where the input type allows one (a duplicate email or username, a blank title), an unauthenticated call, and a missing resource such as an unknown slug.

It runs `./gradlew clean test`, the same command the repository's `gradle.yml` workflow runs on Java 11, until it passes. The build also applies the Spotless plugin to every Java file, so the agent runs `./gradlew spotlessApply` before the final test run and the pull request does not fail on formatting.

## Start it with one API call

`POST https://api.prix.dev/o/{org}/sessions`

```json
{
  "agent": "claude",
  "input": "Write tests for every GraphQL query and mutation this app exposes. Read the GraphQL schema first and list every operation, then read each resolver and the GraphQL exception handler to see what each error case returns. For each operation, test the happy path, validation errors where the input allows them, unauthenticated calls, and missing resources. Send requests through the GraphQL framework's own test executor so the real schema, resolvers and error handling run, and use a real test database, applying the project's migrations, never a mocked repository. Sign in for authenticated calls the way the app's auth filter expects. Assert what the app returns today, and where two similar errors are reported differently, report it in the pull request instead of changing the app. Install the Java version CI uses, run the formatter and the full test suite until it passes, and open a pull request.",
  "resources": [
    {
      "url": "https://github.com/your-org/your-app"
    }
  ]
}
```

1. **Get an API key.** In the console, open Settings, then API keys, and create a key for your org. Send it in the Authorization header as a Bearer token.
2. **Send the request.** POST the body in request.json to https://api.prix.dev/o/{org}/sessions, with your repository as the resources URL. The run starts in its own cloud sandbox.
3. **Read what came back.** Follow the run on its live stream or wait for the run.completed webhook, then read the result and its trajectory.

## What comes back

What comes back is a pull request on its own branch. For an app like this one, the diff adds test classes under `src/test/java/io/spring/graphql/`, roughly one per resolver class (users and login, profiles and follows, articles and the feed, favourites, comments, tags), plus a small helper that creates a user and returns a signed token. The description has a table of the 18 operations with the cases each one covers, and a findings section: for example, that an unauthenticated `createArticle` is reported differently from a failed `login`.

The session transcript keeps every command with its output: the JDK install, each `./gradlew clean test` run with its failures, `spotlessApply`, and the final passing run with the Gradle test summary. The pull request's checks run the repository's `gradle.yml` on Java 11, which runs the same `./gradlew clean test`.

Before merging, a reviewer checks that each error test asserts the error type or the `Error` payload the client actually reads, not only that some error appeared, and decides whether the findings deserve a separate fix.

## Run it on every schema change

When a pull request edits `schema.graphqls`, the new or changed operations need tests. A step in your CI or a GitHub webhook handler on your server can send `POST /o/{org}/sessions` with that pull request's URL in `resources`, and the agent adds tests for exactly the operations that changed, on the same branch.

With a `webhook_url` on the API key, Rush posts `run.completed` or `run.failed` when the turn ends. If the reviewer wants another case, a follow-up to `POST /o/{org}/sessions/{id}/messages` wakes the same sandbox with the build already warm.

## Why run it on Rush?

Testing every operation through the real GraphQL layer means a full Spring context, Flyway migrations and many Gradle runs on Java 11, which on a laptop competes with whatever JDK and IDE the developer has open. In a Rush sandbox the agent installs the JDK CI uses on a machine that exists for this run and repeats `./gradlew clean test` as often as the tests need.

The record is the proof: every Gradle run is kept with its output, so the reviewer sees each failure that shaped a test and the final summary, and the findings about inconsistent errors come with the test that shows them rather than a description.

## Frequently asked questions

### Does it test through HTTP or call the resolvers directly?

Through the GraphQL executor, `DgsQueryExecutor` for a DGS app, inside a full `@SpringBootTest` context. That runs the schema, resolvers, exception handler and database together, which is what a client sees, without starting a network server.

### What database do the tests use?

The one your test profile configures. This app's `application-test.properties` uses SQLite in memory with Flyway applying the real migration, so every test runs against the real schema. For a Postgres app the agent starts Postgres in the sandbox or uses Testcontainers if the project already does.

### Will it change how errors are returned?

No. The tests assert today's behaviour. When two similar failures come back differently, such as an unauthenticated mutation and a bad login, the agent reports it in the pull request so you can decide whether to change the exception handler.

### Does it work with Spring for GraphQL instead of Netflix DGS?

Yes. With Spring for GraphQL the agent uses `HttpGraphQlTester` or `ExecutionGraphQlServiceTester` instead of `DgsQueryExecutor`; the cases it covers per operation are the same.
