Skip to content

Commit 797f75f

Browse files
committed
Docs: Add docs / guides for postgres test container setup
1 parent af443ef commit 797f75f

2 files changed

Lines changed: 294 additions & 0 deletions

File tree

docs/guides/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ existing Maven project. Complete the steps in order.
1111
|------|-------|-------------|
1212
| 1 | [Maven POM setup](add-ebean-postgres-maven-pom.md) | Add Ebean dependencies, the enhancement plugin, and the querybean-generator annotation processor to `pom.xml` |
1313
| 2 | [Database configuration](add-ebean-postgres-database-config.md) | Configure the Ebean `Database` bean using `DataSourceBuilder` and `DatabaseConfig` with Avaje Inject |
14+
| 3 | [Test container setup](add-ebean-postgres-test-container.md) | Start a PostgreSQL (or PostGIS) Docker container for tests using `@TestScope @Factory` with Avaje Inject; covers image mirror, read-only datasource, and PostGIS variant |
Lines changed: 293 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,293 @@
1+
# Guide: Add Ebean ORM (PostgreSQL) to an Existing Maven Project — Step 3: Test Container Setup
2+
3+
## Purpose
4+
5+
This guide provides step-by-step instructions for setting up a PostgreSQL Docker
6+
container for tests using `ebean-test-containers`, exposing an `io.ebean.Database`
7+
bean via an Avaje Inject `@TestScope @Factory` class. This is Step 3 of 3.
8+
9+
Two variants are covered:
10+
- **Variant A** — plain PostgreSQL
11+
- **Variant B** — PostgreSQL with PostGIS extension
12+
13+
---
14+
15+
## Prerequisites
16+
17+
- **Step 1 complete**: `pom.xml` includes `ebean-postgres`, `ebean-maven-plugin`,
18+
`querybean-generator`, and **`ebean-test`** as a test-scoped dependency
19+
(see `add-ebean-postgres-maven-pom.md`)
20+
- **Step 2 complete**: A production `Database` bean exists (see `add-ebean-postgres-database-config.md`)
21+
- **Avaje Inject** is on the classpath with test support (`io.avaje:avaje-inject-test`)
22+
- **Docker** is installed and running on the developer machine
23+
24+
---
25+
26+
## Overview: Declarative vs Programmatic approach
27+
28+
`ebean-test` supports two ways to configure the test database:
29+
30+
| Approach | How | Best for |
31+
|----------|-----|---------|
32+
| **Declarative** | `src/test/resources/application-test.yaml` | Simple projects with no DI, no image mirrors |
33+
| **Programmatic** | `@TestScope @Factory` class | Avaje Inject tests, private image mirrors (ECR), more control |
34+
35+
This guide uses the **programmatic approach** because it integrates naturally with
36+
Avaje Inject, allows a private mirror to be specified (useful in CI with ECR or similar),
37+
and makes the `Database` injectable into tests.
38+
39+
---
40+
41+
## Step 1 — Verify ebean-test is a test dependency
42+
43+
Confirm the following is present in `pom.xml` (added in Step 1):
44+
45+
```xml
46+
<dependency>
47+
<groupId>io.ebean</groupId>
48+
<artifactId>ebean-test</artifactId>
49+
<version>${ebean.version}</version>
50+
<scope>test</scope>
51+
</dependency>
52+
```
53+
54+
`ebean-test` transitively brings in `ebean-test-containers` which provides
55+
`PostgresContainer` and `PostgisContainer`.
56+
57+
---
58+
59+
## Step 2 — Create a `@TestScope @Factory` class
60+
61+
Create a new class in the test source tree (e.g., `src/test/java/.../testconfig/TestConfiguration.java`).
62+
Annotate it with `@TestScope` and `@Factory` so Avaje Inject uses it only in tests.
63+
64+
```java
65+
package com.example.testconfig;
66+
67+
import io.avaje.inject.Bean;
68+
import io.avaje.inject.Factory;
69+
import io.avaje.inject.test.TestScope;
70+
import io.ebean.Database;
71+
72+
@TestScope
73+
@Factory
74+
class TestConfiguration {
75+
// bean methods added in the steps below
76+
}
77+
```
78+
79+
---
80+
81+
## Step 3 — Add a container bean and a Database bean
82+
83+
### Variant A — Plain PostgreSQL
84+
85+
```java
86+
import io.ebean.test.containers.PostgresContainer;
87+
88+
@TestScope
89+
@Factory
90+
class TestConfiguration {
91+
92+
@Bean
93+
PostgresContainer postgres() {
94+
return PostgresContainer.builder("17") // Postgres image version
95+
.dbName("my_app") // database to create inside the container
96+
.build()
97+
.start();
98+
}
99+
100+
@Bean
101+
Database database(PostgresContainer container) {
102+
return container.ebean()
103+
.builder()
104+
.build();
105+
}
106+
}
107+
```
108+
109+
### Variant B — PostGIS (PostgreSQL + PostGIS extension)
110+
111+
Use `PostgisContainer` instead of `PostgresContainer`. The default image is
112+
`ghcr.io/baosystems/postgis:{version}` and the extensions `hstore`, `pgcrypto`,
113+
and `postgis` are installed automatically.
114+
115+
```java
116+
import io.ebean.test.containers.PostgisContainer;
117+
118+
@TestScope
119+
@Factory
120+
class TestConfiguration {
121+
122+
@Bean
123+
PostgisContainer postgres() {
124+
return PostgisContainer.builder("17") // PostGIS image version (Postgres 17)
125+
.dbName("my_app")
126+
.build()
127+
.start();
128+
}
129+
130+
@Bean
131+
Database database(PostgisContainer container) {
132+
return container.ebean()
133+
.builder()
134+
.build();
135+
}
136+
}
137+
```
138+
139+
### Key differences from Variant A
140+
141+
| | PostgresContainer | PostgisContainer |
142+
|---|---|---|
143+
| Docker image | `postgres:{version}` | `ghcr.io/baosystems/postgis:{version}` |
144+
| Default extensions | `hstore, pgcrypto` | `hstore, pgcrypto, postgis` |
145+
| Default port | 6432 | 6432 |
146+
| Optional LW mode || `.useLW(true)` (see Optional section) |
147+
148+
---
149+
150+
## Step 4 — Write a test
151+
152+
Annotate the test class with `@InjectTest` and inject `Database` with `@Inject`:
153+
154+
```java
155+
package com.example.testconfig;
156+
157+
import io.avaje.inject.test.InjectTest;
158+
import io.ebean.Database;
159+
import jakarta.inject.Inject;
160+
import org.junit.jupiter.api.Test;
161+
162+
import static org.assertj.core.api.Assertions.assertThat;
163+
164+
@InjectTest
165+
class DatabaseTest {
166+
167+
@Inject
168+
Database database;
169+
170+
@Test
171+
void database_isAvailable() {
172+
assertThat(database).isNotNull();
173+
}
174+
}
175+
```
176+
177+
---
178+
179+
## Verification
180+
181+
Run the tests:
182+
183+
```bash
184+
mvn test -pl <your-module>
185+
```
186+
187+
Expected log output confirming the container started and Ebean connected:
188+
189+
```
190+
INFO Container ut_postgres running with port:6432 ...
191+
INFO connectivity confirmed for ut_postgres
192+
INFO DataSourcePool [my_app] autoCommit[false] ...
193+
INFO DatabasePlatform name:my_app platform:postgres
194+
INFO Executing db-create-all.sql - ...
195+
```
196+
197+
---
198+
199+
## Optional configurations
200+
201+
### Image mirror (for CI / private registry)
202+
203+
If CI builds pull images from a private registry (e.g., AWS ECR) instead of Docker Hub
204+
or GitHub Container Registry, specify a mirror. The mirror is **only used in CI**
205+
it is ignored on local developer machines (where Docker Hub / GHCR is used directly).
206+
207+
```java
208+
@Bean
209+
PostgresContainer postgres() {
210+
return PostgresContainer.builder("16")
211+
.dbName("my_app")
212+
.mirror("123456789.dkr.ecr.ap-southeast-2.amazonaws.com/mirrored")
213+
.build()
214+
.start();
215+
}
216+
```
217+
218+
Alternatively, set the mirror globally via a system property or
219+
`ebean.test.containers.mirror` in a properties file, avoiding code changes per project.
220+
221+
### Read-only datasource (for tests using read-replica simulation)
222+
223+
Call `.autoReadOnlyDataSource(true)` on the `DatabaseBuilder` to automatically
224+
create a second read-only datasource pointing at the same container:
225+
226+
```java
227+
@Bean
228+
Database database(PostgresContainer container) {
229+
return container.ebean()
230+
.builder()
231+
.autoReadOnlyDataSource(true) // test read-only queries against same container
232+
.build();
233+
}
234+
```
235+
236+
### Dump metrics on shutdown
237+
238+
Useful for performance analysis during test runs:
239+
240+
```java
241+
@Bean
242+
Database database(PostgresContainer container) {
243+
return container.ebean()
244+
.builder()
245+
.dumpMetricsOnShutdown(true)
246+
.dumpMetricsOptions("loc,sql,hash")
247+
.build();
248+
}
249+
```
250+
251+
### PostGIS: LW mode (HexWKB)
252+
253+
For PostGIS with DriverWrapperLW (HexWKB binary geometry encoding), set `.useLW(true)`.
254+
This switches the JDBC URL prefix to `jdbc:postgresql_lwgis://` and requires the
255+
`net.postgis:postgis-jdbc` dependency on the test classpath:
256+
257+
```xml
258+
<!-- add to pom.xml test dependencies when using useLW(true) -->
259+
<dependency>
260+
<groupId>net.postgis</groupId>
261+
<artifactId>postgis-jdbc</artifactId>
262+
<version>2024.1.0</version>
263+
<scope>test</scope>
264+
</dependency>
265+
```
266+
267+
```java
268+
@Bean
269+
PostgisContainer postgres() {
270+
return PostgisContainer.builder("16")
271+
.dbName("my_app")
272+
.useLW(true) // use HexWKB + DriverWrapperLW
273+
.build()
274+
.start();
275+
}
276+
```
277+
278+
> **Note**: LW mode is not required for most PostGIS use cases. Only enable it if
279+
> your entities use binary geometry types (e.g., `net.postgis.jdbc.geometry.Geometry`)
280+
> that require the `DriverWrapperLW` driver.
281+
282+
---
283+
284+
## Keeping the container running (local development)
285+
286+
By default, `ebean-test` stops the Docker container when tests finish. To keep it
287+
running between test runs (much faster for local development), create a marker file:
288+
289+
```bash
290+
mkdir -p ~/.ebean && touch ~/.ebean/ignore-docker-shutdown
291+
```
292+
293+
On CI servers, omit this file so containers are cleaned up after each build.

0 commit comments

Comments
 (0)