#TestContainers integration testing in dagger?

1 messages · Page 1 of 1 (latest)

hollow phoenix
#

Hi all,

I'm attempting to have my dagger builds run TestContainers based integration tests but I'm failing to get them to communicate with my containerized dependencies.

Are there any general guidelines on how to get this scenario to work?

Best regards

final bramble
#

Hi @hollow phoenix there are basically 2 options:

  1. Compat. Run testcontainers unchanged, run an ephemeral docker engine alongside it, orchestrated by Dagger.

  2. Replacement. Instead of using testcontainers, use Dagger directly. It requires one-time code change, but gives you native support for all Dagger's features, and removes the dependency on the Docker engine.

If you have a lot of TestContainers usage, I recommend starting with 1, then gradually moving towards 2.

hollow phoenix
#

the TestContainers are invoked by my test suite, I don't think dagger can be much help here. Can you elaborate on option 1 please?

final bramble
#

Dagger can be of much help. BUT compatibility and continuity are important, so you shouldn't feel obligated to switch either

#

Option 1: Dagger runs the docker engine itself as a (privileged) container. Hook it up your test execution container, via Container.withServiceBinding. Configure DOCKER_HOST inside your container to connect to that engine. Then all docker clients inside your test execution container (including testcontainers) will connect to the ephemeral engine

#

Option 2: import the Dagger SDK directly from your test code. Build and run containers at your leisure, connect to them directly. Like TestContainers but with support for just-in-time building, better remote execution, tracing (but also some DX warts which we are ironing out, cc @visual basalt who has been dealing with those)

Here's an example from our own e2e tests: spinning up an ephemeral k3s cluster directly from the test code, via the Dagger Go client library - so we can test our official helm chart. https://github.com/dagger/dagger/blob/main/e2e/helm/helm_test.go#L95

hollow phoenix
#

I believe I've already tried option 1 however my test suite running inside the dagger container failed to communicate with the TestContainers dependency it was attempting to provision

#

This is roughly what I've been doing:

@func()
async build(
  @argument({ defaultPath: "." }) source: Directory,
  base?: Container,
  toolVersion: string = "10.0.0",
  sdkVersion: string = "10.0",
  target: string = "All",
  skip: string | undefined = undefined,
  buildNumber: string | undefined = undefined,
  collectCoverage: boolean | undefined = undefined,
  withDocker: boolean = false
): Promise<Changeset> {

  let cmd: string[] = ["dotnet", "tool", "run", "cloudtek-build", "--target", target];
  if (skip !== undefined)       cmd.push("--skip", skip);
  if (buildNumber !== undefined) cmd.push("--build-number", buildNumber);
  if (collectCoverage === true)  cmd.push("--collect-coverage");

  let container = (base ?? dag.container().from(`mcr.microsoft.com/dotnet/sdk:${sdkVersion}`))
    .withWorkdir("/app")
    .withDirectory(".", source);

  if (withDocker) {
    const dockerd = dag.container()
      .from("docker:dind")
      .withEnvVariable("DOCKER_TLS_CERTDIR", "")
      .withExposedPort(2375)
      .asService({
        useEntrypoint: true,
        insecureRootCapabilities: true
      });

    container = container
      .withServiceBinding("docker", dockerd)
      .withEnvVariable("DOCKER_HOST", "tcp://docker:2375")
      .withEnvVariable("TESTCONTAINERS_RYUK_DISABLED", "true")
      .withEnvVariable("TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE", "/var/run/docker.sock");
  }

  const before = container.directory(".");
  const after = container
    .withExec(["dotnet", "tool", "install", "CloudTek.Build.Tool", "--version", toolVersion])
    .withExec(["dotnet", "tool", "restore"])
    .withExec(cmd)
    .directory(".");

  return after.changes(before);
}
final bramble
#

@hollow phoenix can you share a snippet from where the test suite tries to connect to one of the dependencies? I'm especially curious what the assumptions are on the hostname..

#

One issue with Docker not supporting remote execution very well, is that infra assumptions end up leaking into your test code (for example, where is the dependency running relative to the docker engine and client..)

hollow phoenix
#

Here's my test fixture - this is instantiated before any tests are run, the lifecycle is handled separately. The container in question is Azurite - an Azure emulator: https://testcontainers.com/modules/azurite/?language=dotnet

using Testcontainers.Azurite;

public sealed class AzuriteFixture : IAsyncLifetime
{
    private const string AzuriteImageTag = "3.35.0";

    private readonly AzuriteContainer _container;

    public AzuriteFixture()
    {
        _container = new AzuriteBuilder(
                $"mcr.microsoft.com/azure-storage/azurite:{AzuriteImageTag}")
            .WithCommand("--skipApiVersionCheck")
            .Build();
    }

    public string ConnectionString => _container.GetConnectionString();

    public string BlobEndpoint => _container.GetBlobEndpoint();

    public string QueueEndpoint => _container.GetQueueEndpoint();

    public string TableEndpoint => _container.GetTableEndpoint();

    public async Task InitializeAsync()
    {
        await _container.StartAsync().ConfigureAwait(false);
    }

    public async Task DisposeAsync()
    {
        await _container.DisposeAsync().ConfigureAwait(false);
    }
}