Elastic.TUnit.Elasticsearch 0.13.0

Prefix Reserved
dotnet add package Elastic.TUnit.Elasticsearch --version 0.13.0
                    
NuGet\Install-Package Elastic.TUnit.Elasticsearch -Version 0.13.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Elastic.TUnit.Elasticsearch" Version="0.13.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Elastic.TUnit.Elasticsearch" Version="0.13.0" />
                    
Directory.Packages.props
<PackageReference Include="Elastic.TUnit.Elasticsearch" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Elastic.TUnit.Elasticsearch --version 0.13.0
                    
#r "nuget: Elastic.TUnit.Elasticsearch, 0.13.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Elastic.TUnit.Elasticsearch@0.13.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Elastic.TUnit.Elasticsearch&version=0.13.0
                    
Install as a Cake Addin
#tool nuget:?package=Elastic.TUnit.Elasticsearch&version=0.13.0
                    
Install as a Cake Tool

Elastic.TUnit.Elasticsearch

Write integration tests against Elasticsearch using TUnit and Elastic.Clients.Elasticsearch.

This is the recommended package for most users — it builds on Elastic.TUnit.Elasticsearch.Core and adds a convenience ElasticsearchCluster base class with a pre-configured ElasticsearchClient.

Getting started

Install

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="TUnit" Version="1.15.0" />
    <PackageReference Include="Elastic.TUnit.Elasticsearch" Version="<latest>" />
  </ItemGroup>
</Project>

Elastic.Clients.Elasticsearch and Elastic.TUnit.Elasticsearch.Core are included as transitive dependencies.

Define a cluster

A one-liner is all you need. The base class provides a default Client with debug mode enabled and per-request diagnostics routed to whichever TUnit test is currently executing:

public class MyTestCluster() : ElasticsearchCluster("latest-9");

The cluster downloads, installs, starts, and tears down Elasticsearch automatically.

Write tests

Tests receive the cluster via constructor injection. Access the client directly:

[ClassDataSource<MyTestCluster>(Shared = SharedType.Keyed, Key = nameof(MyTestCluster))]
public class MyTests(MyTestCluster cluster)
{
    [Test]
    public async Task InfoReturnsNodeName()
    {
        var info = await cluster.Client.InfoAsync();

        await Assert.That(info.Name).IsNotNull();
    }
}

SharedType.Keyed ensures the cluster is created once and shared across all test classes that reference the same key.

Run

dotnet run --project MyTests/

Using an external cluster

When developing integration tests, waiting for an ephemeral cluster to start on every run can be slow. You can point tests at an already-running Elasticsearch instance instead.

Environment variables (zero code changes)

Set TEST_ELASTICSEARCH_URL and optionally TEST_ELASTICSEARCH_API_KEY:

# Basic — no authentication
TEST_ELASTICSEARCH_URL=https://localhost:9200 dotnet run --project MyTests/

# With API key authentication
TEST_ELASTICSEARCH_URL=https://localhost:9200 \
TEST_ELASTICSEARCH_API_KEY=your-api-key-here \
dotnet run --project MyTests/

When TEST_ELASTICSEARCH_URL is set, the cluster validates connectivity (GET /) and skips ephemeral startup entirely. The default Client on ElasticsearchCluster automatically picks up the API key.

Programmatic hook

Override TryUseExternalCluster() for custom logic — service discovery, config files, conditional per-developer overrides, etc.:

public class MyTestCluster : ElasticsearchCluster
{
    public MyTestCluster() : base(new ElasticsearchConfiguration("latest-9")) { }

    protected override ExternalClusterConfiguration TryUseExternalCluster()
    {
        var url = Environment.GetEnvironmentVariable("MY_DEV_CLUSTER_URL");
        if (string.IsNullOrEmpty(url))
            return null; // fall through to ephemeral startup

        return new ExternalClusterConfiguration(
            new Uri(url),
            Environment.GetEnvironmentVariable("MY_DEV_CLUSTER_KEY")
        );
    }
}

The resolution order is:

  1. TryUseExternalCluster() override (programmatic hook)
  2. TEST_ELASTICSEARCH_URL environment variable
  3. Start an ephemeral cluster

Inspecting external cluster state

The cluster exposes IsExternal and ExternalApiKey properties:

[Test]
public async Task SomeTest()
{
    if (cluster.IsExternal)
        TestContext.Current.Output.WriteLine("Running against external cluster");

    var info = await cluster.Client.InfoAsync();
    await Assert.That(info.IsValidResponse).IsTrue();
}

Features

Custom client configuration

Override the Client property when you need custom connection settings, authentication, or serialization:

public class MyTestCluster() : ElasticsearchCluster("latest-9")
{
    public override ElasticsearchClient Client => this.GetOrAddClient((c, output) =>
    {
        var pool = new StaticNodePool(c.NodesUris());
        var settings = new ElasticsearchClientSettings(pool)
            .WireTUnitOutput(output)
            .Authentication(new BasicAuthentication("user", "pass"));
        return new ElasticsearchClient(settings);
    });
}

The .WireTUnitOutput(output) extension enables debug mode and routes per-request diagnostics to the current test's output.

When overriding Client and using external clusters, check ExternalApiKey to wire authentication:

public override ElasticsearchClient Client => this.GetOrAddClient((c, output) =>
{
    var settings = new ElasticsearchClientSettings(new StaticNodePool(c.NodesUris()))
        .WireTUnitOutput(output);

    if (ExternalApiKey != null)
        settings = settings.Authentication(new ApiKey(ExternalApiKey));

    return new ElasticsearchClient(settings);
});

For multiple clusters that share the same client setup, use a base class:

public abstract class MyClusterBase : ElasticsearchCluster
{
    protected MyClusterBase() : base(new ElasticsearchConfiguration("latest-9")
    {
        ShowElasticsearchOutputAfterStarted = false,
    }) { }
}

public class ClusterA : MyClusterBase { }
public class ClusterB : MyClusterBase
{
    protected override void SeedCluster()
    {
        Client.Indices.Create("my-index");
    }
}

Both ClusterA and ClusterB inherit the default Client from ElasticsearchCluster.

Bootstrap diagnostics

During cluster startup the library writes progress to the terminal, bypassing TUnit's per-test output capture so you always see what is happening.

ShowBootstrapDiagnostics Environment Output
null (default) CI Full verbose, ANSI-colored
null (default) Interactive terminal Periodic heartbeat every 5 s
true Any Full verbose, ANSI-colored
false Any Silent

Override the default:

public class MyCluster : ElasticsearchCluster(new ElasticsearchConfiguration("latest-9")
{
    ShowBootstrapDiagnostics = true,   // force full output locally
    ProgressInterval = TimeSpan.FromSeconds(3),
});

On failure, node-level diagnostics (started status, port, version, last exception) are written to both the terminal and TUnit's test output.

Per-test client diagnostics

The default Client on ElasticsearchCluster routes per-request diagnostics to TUnit's test output. The client is created once, but each test's request/response diagnostics appear in that test's output via TestContext.Current.

Version-based skip

[SkipVersion("<8.0.0", "Feature requires 8.x")]
[Test]
public async Task SomeTest() { }

Accepts comma-separated semver ranges. The attribute works on both methods and classes.

Custom skip conditions

public class RequiresLinuxAttribute : SkipTestAttribute
{
    public override bool Skip => !RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
    public override string Reason => "Requires Linux";
}

[RequiresLinux]
[Test]
public async Task LinuxOnlyTest() { }

Concurrency

Cluster startup is serialized. Only one cluster starts at a time across the entire test run, regardless of how many cluster types exist. Elasticsearch is resource-intensive, so startups are gated by an internal semaphore to avoid overwhelming the machine.

Tests run with unlimited parallelism by default. Once a cluster is up, TUnit runs all tests against it concurrently with no limit. For integration tests that hit Elasticsearch this can be too aggressive — use [ParallelLimiter<T>] to cap concurrency:

[ParallelLimiter<ElasticsearchParallelLimit>]
[ClassDataSource<MyTestCluster>(Shared = SharedType.Keyed, Key = nameof(MyTestCluster))]
public class HeavyTests(MyTestCluster cluster) { }

ElasticsearchParallelLimit defaults to Environment.ProcessorCount. Implement your own IParallelLimit for different concurrency.

Full configuration

For multi-node clusters, plugins, or XPack features use ElasticsearchConfiguration directly. When extending the generic ElasticsearchCluster<TConfiguration>, define your own Client property since the default is only on the non-generic base:

public class SecurityCluster : ElasticsearchCluster<ElasticsearchConfiguration>
{
    public SecurityCluster() : base(new ElasticsearchConfiguration(
        version: "latest-9",
        features: ClusterFeatures.XPack | ClusterFeatures.Security,
        numberOfNodes: 2)
    {
        StartTimeout = TimeSpan.FromMinutes(4),
    }) { }

    public ElasticsearchClient Client => this.GetOrAddClient((c, output) =>
    {
        var settings = new ElasticsearchClientSettings(new StaticNodePool(c.NodesUris()))
            .WireTUnitOutput(output);
        return new ElasticsearchClient(settings);
    });

    protected override void SeedCluster()
    {
        // Called after the cluster is healthy -- create indices, seed data, etc.
    }
}

Comparison with Elastic.Elasticsearch.Xunit

Concept Xunit TUnit
Test framework registration [assembly: TestFramework(...)] Not needed
Cluster fixture IClusterFixture<T> [ClassDataSource<T>]
Integration test marker [I] [Test]
Unit test marker [U] [Test]
Current cluster access ElasticXunitRunner.CurrentCluster Constructor injection
Client access cluster.GetOrAddClient(...) in test cluster.Client on cluster
Parallel control ElasticXunitRunOptions.MaxConcurrency [ParallelLimiter<T>]
Cluster partitioning Nullean.Xunit.Partitions SharedType.Keyed
External cluster IntegrationTestsMayUseAlreadyRunningNode TEST_ELASTICSEARCH_URL env var or TryUseExternalCluster() override
Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.13.0 98 2/22/2026
0.12.2 97 2/19/2026