Skip to content

OpenSearch

OpenSearch is an open-source, enterprise-grade search and observability suite that brings order to unstructured data at scale.

Add the following dependency to your project file:

NuGet
1
dotnet add package Testcontainers.OpenSearch

You can start an OpenSearch container instance from any .NET application. To create and start a container instance with the default configuration, use the module-specific builder as shown below:

1
2
var openSearchContainer = new OpenSearchBuilder().Build();
await openSearchContainer.StartAsync();

This example uses xUnit.net's IAsyncLifetime interface to manage the lifecycle of the container. The container is started in the InitializeAsync method before the test method runs, ensuring that the environment is ready for testing. After the test completes, the container is removed in the DisposeAsync method.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public abstract class OpenSearchContainerTest : IAsyncLifetime
{
    private const string IndexName = "testcontainers";

    private readonly OpenSearchContainer _openSearchContainer;

    private OpenSearchContainerTest(OpenSearchContainer openSearchContainer)
    {
        _openSearchContainer = openSearchContainer;
    }

    public async ValueTask InitializeAsync()
    {
        await _openSearchContainer.StartAsync()
            .ConfigureAwait(false);
    }

    public async ValueTask DisposeAsync()
    {
        await DisposeAsyncCore()
            .ConfigureAwait(false);

        GC.SuppressFinalize(this);
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
[UsedImplicitly]
public sealed class InsecureNoAuthConfiguration : OpenSearchContainerTest
{
    public InsecureNoAuthConfiguration()
        : base(new OpenSearchBuilder()
            .WithSecurityEnabled(false)
            .Build())
    {
    }

    protected override OpenSearchClient CreateClient()
    {
        var connectionString = new Uri(_openSearchContainer.GetConnectionString());
        Assert.Equal(Uri.UriSchemeHttp, connectionString.Scheme);
        return new OpenSearchClient(connectionString);
    }
}
1
2
3
4
5
6
7
8
9
[UsedImplicitly]
public sealed class SslBasicAuthDefaultCredentialsConfiguration : OpenSearchContainerTest
{
    public SslBasicAuthDefaultCredentialsConfiguration()
        : base(new OpenSearchBuilder()
            .Build())
    {
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
[UsedImplicitly]
public sealed class SslBasicAuthCustomCredentialsConfiguration : OpenSearchContainerTest
{
    public SslBasicAuthCustomCredentialsConfiguration()
        : base(new OpenSearchBuilder()
            .WithPassword(new string(OpenSearchBuilder.DefaultPassword.Reverse().ToArray()))
            .Build())
    {
    }
}

How to check if the client has established a connection:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
[Fact]
[Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))]
public async Task PingReturnsValidResponse()
{
    // Given
    var client = CreateClient();

    // When
    var response = await client.PingAsync(ct: TestContext.Current.CancellationToken)
        .ConfigureAwait(true);

    // Then
    Assert.True(response.IsValid);
}

Creating an index and alias:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
[Fact]
[Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))]
public async Task ShouldCreateIndexAndAlias()
{
    // Given
    var client = CreateClient();

    var index = Indices.Index(IndexName);

    var alias = new Name(IndexName + "-alias");

    // When
    var createIndexResponse = await CreateIndexAsync(client)
        .ConfigureAwait(true);

    var createAliasResponse = await client.Indices.PutAliasAsync(index, alias, ct: TestContext.Current.CancellationToken)
        .ConfigureAwait(true);

    // Then
    Assert.True(createIndexResponse.IsValid);
    Assert.True(createAliasResponse.IsValid);
}
1
2
3
4
5
6
7
private static Task<CreateIndexResponse> CreateIndexAsync(OpenSearchClient client)
{
    Func<CreateIndexDescriptor, ICreateIndexRequest> createIndexRequest = c =>
        c.Settings(s => s.NumberOfReplicas(0).NumberOfShards(1)).Map<Document>(m => m.AutoMap());

    return client.Indices.CreateAsync(Indices.Index(IndexName), createIndexRequest, TestContext.Current.CancellationToken);
}

Indexing and searching a document:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
[Fact]
[Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))]
public async Task ShouldIndexAndSearchForDocument()
{
    // Given
    var client = CreateClient();

    var document = new Document(Guid.NewGuid().ToString(), Guid.NewGuid().ToString());

    // When
    Func<IndexDescriptor<Document>, IIndexRequest<Document>> indexRequest = i =>
        i.Index(IndexName).Id(document.Id).Refresh(Refresh.True);

    Func<SearchDescriptor<Document>, ISearchRequest> searchRequest = s =>
        s.Index(IndexName).Query(q => q.Match(m => m.Field("title").Query(document.Title)));

    var createIndexResponse = await CreateIndexAsync(client)
        .ConfigureAwait(true);

    var indexResponse = await client.IndexAsync(document, indexRequest, TestContext.Current.CancellationToken)
        .ConfigureAwait(true);

    var searchResponse = await client.SearchAsync(searchRequest, TestContext.Current.CancellationToken)
        .ConfigureAwait(true);

    // Then
    Assert.True(createIndexResponse.IsValid);

    Assert.True(indexResponse.IsValid);
    Assert.Equal(document.Id, indexResponse.Id);

    Assert.True(searchResponse.IsValid);
    Assert.Single(searchResponse.Documents, item => document.Id.Equals(item.Id));
}

The test example uses the following NuGet dependencies:

1
2
3
4
5
<PackageReference Include="Microsoft.NET.Test.Sdk"/>
<PackageReference Include="coverlet.collector"/>
<PackageReference Include="xunit.runner.visualstudio"/>
<PackageReference Include="xunit.v3"/>
<PackageReference Include="OpenSearch.Client"/>

To execute the tests, use the command dotnet test from a terminal.

Tip

For the complete source code of this example and additional information, please refer to our test projects.