diff --git a/examples/Flyway/Directory.Packages.props b/examples/Flyway/Directory.Packages.props index 8549bb998..a78fdaa45 100644 --- a/examples/Flyway/Directory.Packages.props +++ b/examples/Flyway/Directory.Packages.props @@ -5,11 +5,11 @@ - - - - + + + + - + \ No newline at end of file diff --git a/examples/Respawn/.editorconfig b/examples/Respawn/.editorconfig new file mode 100644 index 000000000..6f066619d --- /dev/null +++ b/examples/Respawn/.editorconfig @@ -0,0 +1 @@ +root = true \ No newline at end of file diff --git a/examples/Respawn/.gitattributes b/examples/Respawn/.gitattributes new file mode 100644 index 000000000..212566614 --- /dev/null +++ b/examples/Respawn/.gitattributes @@ -0,0 +1 @@ +* text=auto \ No newline at end of file diff --git a/examples/Respawn/Directory.Build.props b/examples/Respawn/Directory.Build.props new file mode 100644 index 000000000..1bf52aaea --- /dev/null +++ b/examples/Respawn/Directory.Build.props @@ -0,0 +1,25 @@ + + + + 0.1.0 + $(AssemblyName) + $(Version) + $(Version) + $(Version) + en-US + Andre Hofmeister + + + + + + + + git + https://github.com/testcontainers/testcontainers-dotnet-sample + + + 10.0 + enable + + \ No newline at end of file diff --git a/examples/Respawn/Directory.Packages.props b/examples/Respawn/Directory.Packages.props new file mode 100644 index 000000000..630377148 --- /dev/null +++ b/examples/Respawn/Directory.Packages.props @@ -0,0 +1,16 @@ + + + + true + + + + + + + + + + + + \ No newline at end of file diff --git a/examples/Respawn/README.md b/examples/Respawn/README.md new file mode 100644 index 000000000..e51c3f0c6 --- /dev/null +++ b/examples/Respawn/README.md @@ -0,0 +1,9 @@ +# Testcontainers for .NET Respawn example + +This example demonstrates how to use Respawn to reset the state of a PostgreSQL database between tests, ensuring isolation and consistency. The `RespawnTest` test class interacts with a pre-configured database, using Respawn to clear the data before each test run. This allows the tests to execute in a clean environment without interfering with each other. + +```console +git clone --branch develop git@github.com:testcontainers/testcontainers-dotnet.git +cd ./testcontainers-dotnet/examples/Respawn/ +dotnet test Respawn.sln --configuration=Release +``` \ No newline at end of file diff --git a/examples/Respawn/Respawn.sln b/examples/Respawn/Respawn.sln new file mode 100644 index 000000000..78c94ff15 --- /dev/null +++ b/examples/Respawn/Respawn.sln @@ -0,0 +1,26 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{424CFC36-D6F7-4DBB-BD1C-0C84FE30E665}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Respawn.Tests", "tests\Respawn.Tests\Respawn.Tests.csproj", "{75E66E21-E169-4504-89FA-4784CB66DEB5}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {75E66E21-E169-4504-89FA-4784CB66DEB5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {75E66E21-E169-4504-89FA-4784CB66DEB5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {75E66E21-E169-4504-89FA-4784CB66DEB5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {75E66E21-E169-4504-89FA-4784CB66DEB5}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {75E66E21-E169-4504-89FA-4784CB66DEB5} = {424CFC36-D6F7-4DBB-BD1C-0C84FE30E665} + EndGlobalSection +EndGlobal diff --git a/examples/Respawn/src/.gitkeep b/examples/Respawn/src/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/examples/Respawn/tests/Respawn.Tests/DbFixture.cs b/examples/Respawn/tests/Respawn.Tests/DbFixture.cs new file mode 100644 index 000000000..f381a6129 --- /dev/null +++ b/examples/Respawn/tests/Respawn.Tests/DbFixture.cs @@ -0,0 +1,30 @@ +namespace Respawn.Tests; + +[UsedImplicitly] +public sealed class DbFixture : IAsyncLifetime +{ + private readonly IContainer _postgreSqlContainer; + + public DbFixture() + { + // Testcontainers starts the dependent database (PostgreSQL) and copies the SQL scripts + // to the container before it starts. The PostgreSQL container runs the scripts + // automatically during startup, creating the database schema. + _postgreSqlContainer = new PostgreSqlBuilder() + .WithImage("postgres:15-alpine") + .WithResourceMapping("migrate/", "/docker-entrypoint-initdb.d/") + .Build(); + } + + public DbConnection DbConnection => new NpgsqlConnection(((PostgreSqlContainer)_postgreSqlContainer).GetConnectionString()); + + public Task InitializeAsync() + { + return _postgreSqlContainer.StartAsync(); + } + + public Task DisposeAsync() + { + return _postgreSqlContainer.DisposeAsync().AsTask(); + } +} \ No newline at end of file diff --git a/examples/Respawn/tests/Respawn.Tests/Respawn.Tests.csproj b/examples/Respawn/tests/Respawn.Tests/Respawn.Tests.csproj new file mode 100644 index 000000000..ca9da0248 --- /dev/null +++ b/examples/Respawn/tests/Respawn.Tests/Respawn.Tests.csproj @@ -0,0 +1,17 @@ + + + net8.0 + + + + + + + + + + + + + + \ No newline at end of file diff --git a/examples/Respawn/tests/Respawn.Tests/RespawnTest.cs b/examples/Respawn/tests/Respawn.Tests/RespawnTest.cs new file mode 100644 index 000000000..ca3a417a1 --- /dev/null +++ b/examples/Respawn/tests/Respawn.Tests/RespawnTest.cs @@ -0,0 +1,60 @@ +namespace Respawn.Tests; + +public sealed class RespawnTest : IClassFixture, IDisposable +{ + private readonly DbConnection _dbConnection; + + public RespawnTest(DbFixture db) + { + _dbConnection = db.DbConnection; + _dbConnection.Open(); + } + + public void Dispose() + { + _dbConnection.Dispose(); + } + + [Theory] + [InlineData("jane_doe", "jane@example.com", 30)] + [InlineData("john_doe", "john@example.com", 30)] + public async Task UsersTableContainsOneUser(string username, string email, int age) + { + // Respawn resets the database and cleans its state. This allows tests to run without + // interfering with each other. Instead of deleting data at the end of a test, rolling + // back a transaction, or creating a new container instance, Respawn resets the database + // to a clean, empty state by intelligently deleting data from tables. + var respawnerOptions = new RespawnerOptions { DbAdapter = DbAdapter.Postgres }; + var respawner = await Respawner.CreateAsync(_dbConnection, respawnerOptions); + await respawner.ResetAsync(_dbConnection); + + // This test runs twice and inserts a record into the `users` table for each run. + // The test counts the number of users and expects it to always be 1. If the database + // state is not clean, the assertion fails. + using var insertCommand = _dbConnection.CreateCommand(); + + var dbParamUsername = insertCommand.CreateParameter(); + dbParamUsername.ParameterName = "@username"; + dbParamUsername.Value = username; + + var dbParamEmail = insertCommand.CreateParameter(); + dbParamEmail.ParameterName = "@email"; + dbParamEmail.Value = email; + + var dbParamAge = insertCommand.CreateParameter(); + dbParamAge.ParameterName = "@age"; + dbParamAge.Value = age; + + insertCommand.CommandText = "INSERT INTO users (username, email, age) VALUES (@username, @email, @age)"; + insertCommand.Parameters.Add(dbParamUsername); + insertCommand.Parameters.Add(dbParamEmail); + insertCommand.Parameters.Add(dbParamAge); + insertCommand.ExecuteNonQuery(); + + using var selectCommand = _dbConnection.CreateCommand(); + selectCommand.CommandText = "SELECT COUNT(*) FROM users"; + var userCount = Convert.ToInt32(selectCommand.ExecuteScalar()); + + Assert.Equal(1, userCount); + } +} \ No newline at end of file diff --git a/examples/Respawn/tests/Respawn.Tests/Usings.cs b/examples/Respawn/tests/Respawn.Tests/Usings.cs new file mode 100644 index 000000000..a6f03e666 --- /dev/null +++ b/examples/Respawn/tests/Respawn.Tests/Usings.cs @@ -0,0 +1,9 @@ +global using System; +global using System.Collections.Generic; +global using System.Data.Common; +global using System.Threading.Tasks; +global using DotNet.Testcontainers.Containers; +global using JetBrains.Annotations; +global using Npgsql; +global using Testcontainers.PostgreSql; +global using Xunit; \ No newline at end of file diff --git a/examples/Respawn/tests/Respawn.Tests/migrate/V1.0__create_users_table.sql b/examples/Respawn/tests/Respawn.Tests/migrate/V1.0__create_users_table.sql new file mode 100644 index 000000000..8cbb71060 --- /dev/null +++ b/examples/Respawn/tests/Respawn.Tests/migrate/V1.0__create_users_table.sql @@ -0,0 +1,7 @@ +CREATE TABLE users +( + id SERIAL PRIMARY KEY, + username VARCHAR(50) NOT NULL, + email VARCHAR(100) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); \ No newline at end of file diff --git a/examples/Respawn/tests/Respawn.Tests/migrate/V1.1__alter_users_table.sql b/examples/Respawn/tests/Respawn.Tests/migrate/V1.1__alter_users_table.sql new file mode 100644 index 000000000..81ee4fe00 --- /dev/null +++ b/examples/Respawn/tests/Respawn.Tests/migrate/V1.1__alter_users_table.sql @@ -0,0 +1 @@ +ALTER TABLE users ADD COLUMN age INT; \ No newline at end of file diff --git a/examples/Respawn/tests/Respawn.Tests/migrate/V2.0__seed_example_users.sql b/examples/Respawn/tests/Respawn.Tests/migrate/V2.0__seed_example_users.sql new file mode 100644 index 000000000..254d5d592 --- /dev/null +++ b/examples/Respawn/tests/Respawn.Tests/migrate/V2.0__seed_example_users.sql @@ -0,0 +1 @@ +-- INSERT INTO users (username, email, age) VALUES ('john_doe', 'john@example.com', 30); \ No newline at end of file