# Write Testcontainers Tests for a Spring Boot App

Yes. An agent writes Testcontainers tests that boot your Spring Boot app against real MySQL and PostgreSQL containers and exercise it over HTTP, then opens a PR.

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

## What the agent does

The agent reads how the app already tests itself before adding anything. In a Spring Boot app like spring-projects/spring-petclinic it finds `spring-boot-starter-parent` 4.1.0 and Java 17 in `pom.xml`, both Maven (`./mvnw`) and Gradle (`build.gradle`) builds, and test dependencies on `spring-boot-testcontainers`, `testcontainers-junit-jupiter` and `testcontainers-mysql`. The controller tests, `OwnerControllerTests`, `PetControllerTests` and `VisitControllerTests`, are `@WebMvcTest` slices with the repository replaced by `@MockitoBean`, so none of them reach a database. `MySqlIntegrationTests` does boot the app against a `MySQLContainer` for `mysql:9.7` with `@ServiceConnection`, but it holds two tests: `findAll` on the vet repository and a `GET /owners/1` that checks for `200`. `PostgresIntegrationTests` uses Spring Boot's Docker Compose support with the `postgres` service in `docker-compose.yml`, not Testcontainers.

So the gap is the full owner and pet flow against each real database. The agent adds `testcontainers-postgresql` beside `testcontainers-mysql` in both `pom.xml` and `build.gradle`, so the two builds stay in step. It writes one abstract test class with the HTTP cases and two subclasses, one with `@ActiveProfiles("mysql")` and a `MySQLContainer`, one with `@ActiveProfiles("postgres")` and a `PostgreSQLContainer` for `postgres:18.4`, the image `docker-compose.yml` pins. Each container is a static `@Container` field with `@ServiceConnection`, so Spring Boot wires the datasource from it without the `MYSQL_URL` or `POSTGRES_URL` properties that `application-mysql.properties` and `application-postgres.properties` read, and `spring.sql.init.mode=always` loads `db/mysql` or `db/postgres` `schema.sql` and `data.sql` into the fresh database.

The tests drive the app the way a browser does, through `@SpringBootTest(webEnvironment = RANDOM_PORT)` and a `RestTemplate`, as the existing integration tests do. They post the `/owners/new` form and follow the redirect to the new owner's page, search `GET /owners?lastName=` and check the page of five that `OwnerController` builds with `PageRequest`, post `/owners/{ownerId}/edit`, add a pet with `/owners/{ownerId}/pets/new` and edit it, and add a visit with `/owners/{ownerId}/pets/{petId}/visits/new`. They also send what the validation should refuse: a blank last name, a telephone that fails the `\d{10}` pattern on `Owner`, and a pet with a duplicate name or a birth date in the future, which `PetValidator` and `PetController` reject. PetClinic has no delete endpoint for owners or pets, so the agent says so instead of inventing one.

Testcontainers needs a Docker daemon. The agent checks for one in its sandbox, where it can install packages with `sudo`, and runs `./mvnw verify`, which also runs the javaformat and checkstyle plugins the build declares. If Docker cannot run there, the classes skip themselves through `@Testcontainers(disabledWithoutDocker = true)`, exactly as `MySqlIntegrationTests` does, and the agent says in the pull request that they were skipped in the sandbox rather than reporting them as passed.

## Start it with one API call

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

```json
{
  "agent": "claude",
  "input": "Add Testcontainers integration tests that run this Spring Boot app against real MySQL and PostgreSQL containers, covering owner and pet CRUD through the HTTP layer. Read pom.xml, build.gradle, the application properties for each database profile and the existing integration tests first, and reuse their style: @SpringBootTest with a random port, @ServiceConnection containers, the same image tags the repository already pins. Put the HTTP cases in one shared class run once per database. Cover creating, finding, paging and editing owners, adding and editing pets and visits, and the validation errors the forms return. Add any Testcontainers module to both the Maven and Gradle builds. Do not change application code. Run ./mvnw verify; if Docker is not available, say that the container tests were skipped, never that they passed. 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

The pull request adds the shared test class and its MySQL and PostgreSQL subclasses under `src/test/java/org/springframework/samples/petclinic/`, and the `testcontainers-postgresql` dependency in `pom.xml` and `build.gradle`. It changes nothing under `src/main`. The existing `MySqlIntegrationTests` and `PostgresIntegrationTests` stay as they are; the description notes that the new classes cover what they check and leaves removing them to the maintainers.

The checks are the evidence. In spring-petclinic the `maven-build.yml` and `gradle-build.yml` workflows run `./mvnw -B verify` and the Gradle build on the pull request, on GitHub runners that have Docker, so both container suites run there. The session keeps the agent's own `./mvnw verify` output from the sandbox, with each test class and whether it ran or was skipped, and the description lists every case by route.

A reviewer checks that the tests do not depend on each other's rows, since both databases start from the same `data.sql` seed and a test that adds an owner must not break a later count, that the container images match what production runs, and that the extra build time of starting two databases is acceptable in CI.

## Run it on every database schema change

When a pull request changes `schema.sql`, a migration or an entity, have a GitHub `pull_request` webhook send this request with the pull request URL in `resources`, and add to the prompt that the tests must cover the changed tables. The agent works on that branch, so a change that runs on H2 but fails on MySQL or PostgreSQL is caught before merge, in its own session and sandbox.

With a webhook URL on the API key, Rush POSTs `run.completed` or `run.failed` when the turn ends, with the session URL, and your server can link the test pull request from the schema change.

## Why run it on Rush?

Running a Spring Boot app against two real databases on a laptop means Docker Desktop, two containers pulled and started, a JVM build, and a working tree tied up until it finishes. On Rush the agent builds with the repository's own Maven wrapper in its own sandbox, and every `./mvnw verify` it ran is kept with its output, including whether the container tests actually ran there or skipped.

That distinction matters for this job. A Testcontainers suite that silently skips looks green, and the pull request says plainly which classes ran where: in the sandbox, on the pull request's own workflows, or both. The same request can run across several Spring services at once, each in its own sandbox, each with a pull request someone reviews.

## Frequently asked questions

### Why not just use H2 for integration tests?

H2 accepts SQL that MySQL and PostgreSQL reject and the other way round, so a test that passes on H2 says little about production. PetClinic keeps separate `schema.sql` files under `db/h2`, `db/mysql` and `db/postgres` for exactly this reason, and the new tests load the real ones into the real engines.

### Does it work with Gradle as well as Maven?

Yes. The agent reads which build files the repository has and adds the Testcontainers modules to each, so `./mvnw verify` and the Gradle build run the same tests. In spring-petclinic both builds are maintained, and both get the dependency.

### What does @ServiceConnection do here?

It tells Spring Boot to read the datasource URL, user and password from the running container instead of from properties. The test needs no `spring.datasource.url` override and no `@DynamicPropertySource` method, and the app connects to whatever port the container was given.

### Will it replace my @WebMvcTest tests?

No. Slice tests with mocked repositories are fast and check controller logic, and they stay. The new tests sit beside them and check what the slices cannot: that the queries, the schema and the forms work together on a real database.
