Testcontainers integration (rift-java-testcontainers)¶
RiftContainer runs the rift-proxy engine in Docker — for teams standardized on
Testcontainers, or CI without native-lib/binary access. It extends
GenericContainer, so it composes with @Testcontainers/@Container like any other container,
and client() hands back a Rift with the hostResolver seam wired so imposter.uri() is
correct through Docker's port remapping — with zero user-side rewriting.
<dependency>
<groupId>io.github.achird-labs</groupId>
<artifactId>rift-java-testcontainers</artifactId>
<scope>test</scope>
</dependency>
The default image is zainalpour/rift-proxy:v<engine-version>, pinned to the same engine version
as the rest of rift-java (RiftContainer.ENGINE_VERSION). Pass a DockerImageName to override it.
Fixed-port mode (default)¶
Pre-expose the imposter ports you'll bind. imposter.uri() then resolves to the mapped host:port:
@Testcontainers
class OrdersTest {
@Container
static RiftContainer rift = new RiftContainer().withImposterPorts(4545);
@Test
void ordersCallUsers() {
try (Rift client = rift.client()) {
Imposter users = client.create(imposter("users").port(4545)
.stub(onGet("/u/1").willReturn(okJson("{\"id\":1}"))));
// users.uri() == http://<host>:<mappedPort(4545)> — point your SUT at it
}
}
}
Gateway mode¶
withGateway() needs no pre-exposed imposter ports: traffic routes through the single admin port
via the engine's /__rift/:port gateway, and imposter.uri() carries that prefix. The trade-off:
one exposed port, but the /__rift/:port prefix is visible to the app under test.
@Container
static RiftContainer rift = new RiftContainer().withGateway();
// imposter.uri() == http://<host>:<mappedAdminPort>/__rift/<port>
API key¶
withApiKey(key) sets the engine's MB_APIKEY (so the admin control plane requires it) and makes
client() authenticate with the same key. The gateway data plane is not gated by the key.
TLS-MITM intercept¶
withInterceptPort(port) starts the engine's intercept listener at launch (via RIFT_INTERCEPT_PORT)
and exposes the port; attach the client to the mapped endpoint with interceptOptions():
@Container
static final RiftContainer rift = new RiftContainer().withInterceptPort(8888);
@Test
void mocksAnHttpsDependency() throws Exception {
try (Rift client = rift.client()) {
Intercept intercept = client.intercept(rift.interceptOptions()); // attach to the mapped port
intercept.serve("api.partner.com", okJson("{\"ok\":true}"));
HttpClient http = HttpClient.newBuilder()
.sslContext(intercept.trust().sslContext()) // trust the container's CA
.proxy(intercept.proxySelector())
.build();
// point the SUT (or this client) at https://api.partner.com/… — served by rift
}
}
See docs/intercept.md for rules, trust material, and shared-CA setups.
Using it with the Spring module¶
Publish the container's admin URI as a property and point @EnableRift(transport = CONNECT) at it:
@SpringBootTest
@EnableRift(transport = Transport.CONNECT, adminUri = "${rift.container.admin}")
@ConfigureImposter(name = "users", baseUrlProperty = "user-client.base-url")
@Testcontainers
class UserServiceIT {
@Container
static RiftContainer rift = new RiftContainer().withImposterPorts(4545);
@DynamicPropertySource
static void riftProps(DynamicPropertyRegistry registry) {
registry.add("rift.container.admin", () -> rift.adminUri().toString());
}
// @InjectImposter("users") / @InjectRift work exactly as in any @EnableRift test
}
Running the integration tests¶
RiftContainer's own round-trip tests are gated on the RIFT_IT environment variable (like
rift-conformance), so they skip cleanly where Docker isn't available. Run them with a Docker daemon
present:
RIFT_IT=1 ./mvnw -pl rift-java-testcontainers -am test