Skip to content

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
C#
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:

C#
var source = g
    .UseGremlinServer<Vertex, Edge>(_ => _
        .UseGremlinServerModContainer()
        .UseNewtonsoftJson());
JanusGraph — janusgraph/janusgraph
C#
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.

C#
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:

  1. ConfigureContainer — receives a ContainerBuilder and returns the configured builder. Set the image, port bindings, wait strategy, environment variables, and anything else the Testcontainers API supports.

  2. ConfigureClientFactory — receives the pool client factory and the running IContainer once the container has started. Use container.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:

C#
public sealed class AirportTestFixture
{
    public IGremlinQuerySource QuerySource { get; } = g
        .UseGremlinServer<Vertex, Edge>(_ => _
            .UseGremlinServerContainer("3.7.6")
            .UseNewtonsoftJson());
}

Querying from a test is then straightforward:

C#
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.