Java API tests
An API test exercises the application through HTTP. @ShadowPg starts pgmem, directs the application’s normal DataSource to it, and restores data before each test. The DataSource and pool remain alive throughout the class.
Start an HTTP server on a random port
Section titled “Start an HTTP server on a random port”These examples assume the application has POST /orders and GET /orders endpoints. Put db/schema.sql in src/test/resources.
@ShadowPg(schema = "db/schema.sql")class OrdersApiTest { @LocalServerPort int port; HttpClient http = HttpClient.newHttpClient();
@Test void createsOrderThroughHttp() throws Exception { HttpRequest request = HttpRequest.newBuilder(URI.create(baseUrl() + "/orders")) .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString("{\"sku\":\"book\"}")) .build(); assertEquals(201, http.send(request, HttpResponse.BodyHandlers.ofString()).statusCode()); }
@Test void startsFromPreparedDataAgain() throws Exception { HttpRequest request = HttpRequest.newBuilder(URI.create(baseUrl() + "/orders")).build(); var response = http.send(request, HttpResponse.BodyHandlers.ofString()); assertEquals(200, response.statusCode()); assertEquals("[]", response.body()); }
String baseUrl() { return "http://127.0.0.1:" + port; }}@ShadowPg(schema = "db/schema.sql")class OrdersApiTest { @Inject EmbeddedServer app; HttpClient http = HttpClient.newHttpClient();
@Test void createsOrderThroughHttp() throws Exception { HttpRequest request = HttpRequest.newBuilder(app.getURI().resolve("/orders")) .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString("{\"sku\":\"book\"}")) .build(); assertEquals(201, http.send(request, HttpResponse.BodyHandlers.ofString()).statusCode()); }
@Test void startsFromPreparedDataAgain() throws Exception { HttpRequest request = HttpRequest.newBuilder(app.getURI().resolve("/orders")).build(); var response = http.send(request, HttpResponse.BodyHandlers.ofString()); assertEquals(200, response.statusCode()); assertEquals("[]", response.body()); }}Add testImplementation("io.github.shibukawa.pgmem:pgmem-spring:1.18.0") for Spring Boot or testImplementation("io.github.shibukawa.pgmem:pgmem-micronaut:1.18.0") for Micronaut, alongside the app’s normal framework test dependencies. Import io.github.shibukawa.pgmem.spring.ShadowPg or io.github.shibukawa.pgmem.micronaut.ShadowPg, respectively. The annotation supplies the fork URL through the framework’s normal DataSource configuration and captures the application’s post-startup state as the reset baseline. If the app runs its own migration at startup, omit schema. Add seed = "db/seed.sql" for initial data. Each test class owns a separate fork and server.
Add the PostgreSQL JDBC driver and platform-specific pgmem-native test dependency shown in Java basics.
Other setups and datasets
Section titled “Other setups and datasets”For a framework that takes a DataSource directly, construct the app with pg.applicationFork().dataSource() before serving requests. For a child JVM, pass pg.applicationFork().jdbcUrl() in its startup configuration before it builds its pool. Keep the same app and fork for sequential cases and call reset() in @BeforeEach.
To create a suite-specific baseline, seed the application fork once, call Snapshot seeded = pg.applicationFork().snapshot(), then use pg.applicationFork().reset(seeded) before each case. Close seeded after the suite.
For browser automation over the same HTTP lifecycle, see E2E tests.
Safe parallel execution
Section titled “Safe parallel execution”Keep methods that use one @ShadowPg Spring Boot or Micronaut app sequential: they share an HTTP server, pool, and fork, and each method resets that fork. Run different test classes concurrently only when each class has its own application context, pgmem fork, and HTTP port. In src/test/resources/junit-platform.properties, enable parallel classes while keeping methods sequential:
junit.jupiter.execution.parallel.enabled = truejunit.jupiter.execution.parallel.mode.default = same_threadjunit.jupiter.execution.parallel.mode.classes.default = concurrentDo not share a static application or DataSource between these classes. Finish requests and background database work, and close transactions before the next method resets the fork. If every method needs to run concurrently, construct a separate app and fork per method instead of using the class-scoped annotation lifecycle.