Skip to content

Commit 4788ed6

Browse files
0xcedHofmeisterAn
andauthored
feat: Add a wait strategy that waits until the ADO.NET database is available (#1401)
Co-authored-by: Andre Hofmeister <[email protected]>
1 parent fff9742 commit 4788ed6

File tree

40 files changed

+439
-251
lines changed

40 files changed

+439
-251
lines changed

docs/modules/cassandra.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,15 @@ Add the following dependency to your project file:
88
dotnet add package Testcontainers.Cassandra
99
```
1010

11-
You can start an Apache Cassandra container instance from any .NET application. 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.
11+
You can start an Apache Cassandra 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:
12+
13+
=== "Start a Cassandra container"
14+
```csharp
15+
var cassandraContainer = new CassandraBuilder().Build();
16+
await cassandraContainer.StartAsync();
17+
```
18+
19+
The following example utilizes the [xUnit.net](/test_frameworks/xunit_net/) module to reduce overhead by automatically managing the lifecycle of the dependent container instance. It creates and starts the container using the module-specific builder and injects it as a shared class fixture into the test class.
1220

1321
=== "Usage Example"
1422
```csharp

docs/modules/db2.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,15 @@ dotnet add package Testcontainers.Db2
1212

1313
The Linux client dependency, [Net.IBM.Data.Db2-lnx](https://www.nuget.org/packages/Net.IBM.Data.Db2-lnx), requires additional configurations. We use the [Testcontainers.Db2.Tests.targets](https://github.com/testcontainers/testcontainers-dotnet/blob/develop/tests/Testcontainers.Db2.Tests/Testcontainers.Db2.Tests.targets) file to configure the environment variables: `LD_LIBRARY_PATH`, `PATH`, `DB2_CLI_DRIVER_INSTALL_PATH`, at runtime.
1414

15-
You can start an Db2 container instance from any .NET application. 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.
15+
You can start a Db2 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:
16+
17+
=== "Start a Db2 container"
18+
```csharp
19+
var db2Container = new Db2Builder().Build();
20+
await db2Container.StartAsync();
21+
```
22+
23+
The following example utilizes the [xUnit.net](/test_frameworks/xunit_net/) module to reduce overhead by automatically managing the lifecycle of the dependent container instance. It creates and starts the container using the module-specific builder and injects it as a shared class fixture into the test class.
1624

1725
=== "Usage Example"
1826
```csharp

docs/modules/mssql.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,15 @@ Add the following dependency to your project file:
88
dotnet add package Testcontainers.MsSql
99
```
1010

11-
You can start a MSSQL container instance from any .NET application. Here, we create different container instances and pass them to the base test class. This allows us to test different configurations.
11+
You can start a MSSQL 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:
1212

13-
=== "Create Container Instance"
13+
=== "Start a MSSQL container"
1414
```csharp
15-
--8<-- "tests/Testcontainers.MsSql.Tests/MsSqlContainerTest.cs:CreateMsSqlContainer"
15+
var msSqlContainer = new MsSqlBuilder().Build();
16+
await msSqlContainer.StartAsync();
1617
```
1718

18-
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.
19+
The following example utilizes the [xUnit.net](/test_frameworks/xunit_net/) module to reduce overhead by automatically managing the lifecycle of the dependent container instance. It creates and starts the container using the module-specific builder and injects it as a shared class fixture into the test class.
1920

2021
=== "Usage Example"
2122
```csharp

docs/modules/postgres.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,15 @@ Add the following dependency to your project file:
88
dotnet add package Testcontainers.PostgreSql
99
```
1010

11-
You can start an PostgreSQL container instance from any .NET application. 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.
11+
You can start a PostgreSQL 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:
12+
13+
=== "Start a PostgreSQL container"
14+
```csharp
15+
var postgreSqlContainer = new PostgreSqlBuilder().Build();
16+
await postgreSqlContainer.StartAsync();
17+
```
18+
19+
The following example utilizes the [xUnit.net](/test_frameworks/xunit_net/) module to reduce overhead by automatically managing the lifecycle of the dependent container instance. It creates and starts the container using the module-specific builder and injects it as a shared class fixture into the test class.
1220

1321
=== "Usage Example"
1422
```csharp

docs/modules/pulsar.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Add the following dependency to your project file:
88
dotnet add package Testcontainers.Pulsar
99
```
1010

11-
You can start a Apache Pulsar container instance from any .NET application. Here, we create different container instances and pass them to the base test class. This allows us to test different configurations.
11+
You can start an Apache Pulsar container instance from any .NET application. Here, we create different container instances and pass them to the base test class. This allows us to test different configurations.
1212

1313
=== "Create Container Instance"
1414
```csharp

docs/modules/qdrant.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Add the following dependency to your project file:
88
dotnet add package Testcontainers.Qdrant
99
```
1010

11-
You can start an Qdrant container instance from any .NET application. 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.
11+
You can start a Qdrant container instance from any .NET application. 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.
1212

1313
=== "Usage Example"
1414
```csharp

src/Testcontainers.Cassandra/CassandraContainer.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,12 @@ public CassandraContainer(CassandraConfiguration configuration)
1616
/// <returns>The Cassandra connection string.</returns>
1717
public string GetConnectionString()
1818
{
19+
var publicPort = GetMappedPublicPort(CassandraBuilder.CqlPort).ToString();
20+
1921
var properties = new Dictionary<string, string>();
2022
properties.Add("Contact Points", Hostname);
21-
properties.Add("Port", GetMappedPublicPort(CassandraBuilder.CqlPort).ToString());
23+
properties.Add("Port", publicPort);
24+
properties.Add("Cluster Name", $"{Hostname}:{publicPort}");
2225
return string.Join(";", properties.Select(property => string.Join("=", property.Key, property.Value)));
2326
}
2427

src/Testcontainers.EventHubs/Usings.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
global using System.Linq;
55
global using System.Text;
66
global using System.Text.Json;
7-
global using System.Threading.Tasks;
87
global using Docker.DotNet.Models;
98
global using DotNet.Testcontainers;
109
global using DotNet.Testcontainers.Builders;

src/Testcontainers/Configurations/WaitStrategies/IWaitForContainerOS.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ namespace DotNet.Testcontainers.Configurations
22
{
33
using System;
44
using System.Collections.Generic;
5+
using System.Data.Common;
56
using System.Text.RegularExpressions;
7+
using DotNet.Testcontainers.Containers;
68
using JetBrains.Annotations;
79

810
/// <summary>
@@ -112,6 +114,18 @@ public interface IWaitForContainerOS
112114
[PublicAPI]
113115
IWaitForContainerOS UntilContainerIsHealthy(long failingStreak = 3, Action<IWaitStrategy> waitStrategyModifier = null);
114116

117+
/// <summary>
118+
/// Waits until a successful connection to the database can be established.
119+
/// </summary>
120+
/// <remarks>
121+
/// To use this wait strategy, the container must implement the <see cref="IDatabaseContainer" /> interface.
122+
/// </remarks>
123+
/// <param name="dbProviderFactory">The <see cref="DbProviderFactory" /> used to create the database connection.</param>
124+
/// <param name="waitStrategyModifier">The wait strategy modifier to cancel the readiness check.</param>
125+
/// <returns>A configured instance of <see cref="IWaitForContainerOS" />.</returns>
126+
[PublicAPI]
127+
IWaitForContainerOS UntilDatabaseIsAvailable(DbProviderFactory dbProviderFactory, Action<IWaitStrategy> waitStrategyModifier = null);
128+
115129
/// <summary>
116130
/// Returns a collection with all configured wait strategies.
117131
/// </summary>
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
namespace DotNet.Testcontainers.Configurations
2+
{
3+
using System;
4+
using System.Data.Common;
5+
using System.Threading.Tasks;
6+
using DotNet.Testcontainers.Containers;
7+
8+
internal class UntilDatabaseIsAvailable : IWaitUntil
9+
{
10+
private readonly DbProviderFactory _dbProviderFactory;
11+
12+
public UntilDatabaseIsAvailable(DbProviderFactory dbProviderFactory)
13+
{
14+
_dbProviderFactory = dbProviderFactory;
15+
}
16+
17+
public async Task<bool> UntilAsync(IContainer container)
18+
{
19+
if (container is not IDatabaseContainer dbContainer)
20+
{
21+
throw new NotSupportedException(
22+
$"The 'UntilDatabaseIsAvailable' wait strategy can only be used with database containers. " +
23+
$"The provided container type '{container.GetType().FullName}' does not implement '{nameof(IDatabaseContainer)}'.");
24+
}
25+
26+
var connection = _dbProviderFactory.CreateConnection();
27+
if (connection == null)
28+
{
29+
throw new InvalidOperationException(
30+
$"Failed to create a database connection. The factory '{_dbProviderFactory.GetType().FullName}' returned null from 'CreateConnection()'.");
31+
}
32+
33+
try
34+
{
35+
connection.ConnectionString = dbContainer.GetConnectionString();
36+
37+
await connection.OpenAsync()
38+
.ConfigureAwait(false);
39+
40+
return true;
41+
}
42+
catch
43+
{
44+
return false;
45+
}
46+
finally
47+
{
48+
connection.Dispose();
49+
}
50+
}
51+
}
52+
}

0 commit comments

Comments
 (0)