TestContainers support
ExRam.Gremlinq.Support.TestContainers lets you run a real graph database inside a Docker
container during integration tests — no external infrastructure required. The container is
started lazily on the first query and torn down when the client is disposed.
Prerequisites
Docker must be installed and running on the machine that executes the tests.
The package depends on Testcontainers (NuGet) and ExRam.Gremlinq.Providers.Core.
Convenience methods
Three ready-made extension methods handle the most common images. Each one binds port 8182,
waits until the internal TCP port is available, and rewrites the WebSocket URI to
ws://localhost:{mappedPort} once the container is up.
They are called on the provider configurator, in the same position where you would call
.AtLocalhost() for a local server:
Gremlin Server — tinkerpop/gremlin-server
var source = g
.UseGremlinServer<Vertex, Edge>(_ => _
.UseGremlinServerContainer("3.7.6")
.UseNewtonsoftJson());
Pass a Docker tag to pin the version (default: latest).
Gremlin Server Mod — ghcr.io/gremlinq/gremlin-server-mod
A Gremlinq-maintained image that adds support for features not available in the
stock Gremlin Server image (see
Gremlinq.Dockerfiles.GremlinServerMod
for details). Defaults to tag 3:
JanusGraph — janusgraph/janusgraph
var source = g
.UseJanusGraph<Vertex, Edge>(_ => _
.UseJanusGraphContainer("1.1.0")
.UseNewtonsoftJson());
Pass a Docker tag to pin the version (default: latest).
The convenience methods work with any provider that uses a WebSocket connection, including Neptune and CosmosDb emulators — just call the appropriate method on the matching provider configurator.
Custom container setup
Use UseTestContainers when the built-in images do not suit your needs. You configure the
container with the full Testcontainers ContainerBuilder API, then get access to the running
IContainer to wire up the connection URI.
var source = g
.UseGremlinServer<Vertex, Edge>(configurator => configurator
.UseTestContainers(c => c
.ConfigureContainer(builder => builder
.WithImage("tinkerpop/gremlin-server:3.7.6")
.WithPortBinding(8182, true)
.WithWaitStrategy(Wait
.ForUnixContainer()
.UntilInternalTcpPortIsAvailable(8182)))
.ConfigureClientFactory((poolFactory, container) => poolFactory
.ConfigureBaseFactory(webSocketFactory => webSocketFactory
.ConfigureUri(_ => new Uri($"ws://localhost:{container.GetMappedPublicPort(8182)}")))))
.UseNewtonsoftJson());
The two steps are:
-
ConfigureContainer— receives aContainerBuilderand returns the configured builder. Set the image, port bindings, wait strategy, environment variables, and anything else the Testcontainers API supports. -
ConfigureClientFactory— receives the pool client factory and the runningIContaineronce the container has started. Usecontainer.GetMappedPublicPort(8182)to obtain the dynamically assigned host port and rewrite the WebSocket URI accordingly.
Container lifecycle
The container is not started at setup time. It is started lazily when the first query is
submitted. Subsequent queries on the same client reuse the running container. The container is
stopped and removed when the IGremlinqClient is disposed (typically when the test fixture or
service scope is torn down).
Because pool configuration (WithPoolSize, WithMaxInProcessPerConnection) is
incompatible with the container lifecycle management, those methods throw
NotSupportedException when called on a container-backed factory. Configure pooling on the
inner factory before passing it to ConfigureClientFactory if needed.
Test fixture pattern
A common pattern is a small fixture class that exposes a shared IGremlinQuerySource:
public sealed class AirportTestFixture
{
public IGremlinQuerySource QuerySource { get; } = g
.UseGremlinServer<Vertex, Edge>(_ => _
.UseGremlinServerContainer("3.7.6")
.UseNewtonsoftJson());
}
Querying from a test is then straightforward:
var airports = await fixture.QuerySource
.V<Airport>()
.Where(a => a.Code == "JFK")
.ToArrayAsync();
The QuerySource is safe to share across tests — the container and client are created once
and reused.
Related: Domain-Specific Languages — building a typed query vocabulary | Gremlinq Options — tuning query behaviour at the environment level.