I'm a huge fan of test driven development. It helps me deliver fewer defects in my apps and gives me confidence that my changes don't break any existing features.
While I write mostly unit tests that run quickly, I try to also have lots of integrations tests for all API endpoints in my web applications. This typically involves code that hits a database, so I had to find some way to easily spin up database instances for my tests. I'm a firm believer that running tests should only involve a single command, so relying on any existing database on the build agent was a hard no for me. Additionally, multiple instances might run in parallel on the same machine, e.g. when different branches are built at the same time.
My initial approach has always involved using a SQLite in memory database, actually a fresh one for every single test. This worked well, was fast enough and dead easy to set up. However, it had one huge problem: I'm not running SQLite in production, so I wasn't really testing the system under the same circumstances and additionally, I could not test features that involved database specific behavior.
Typically in such a situation, most developers would use a containerized database, such as Microsoft SQL Server. But most approaches like this that I've seen were a bit ugly: the database usually had to be spun up manually before running tests, or at least via a build script. But I wanted a way that works independently of how the tests are run - whether via my build script, by running dotnet test or just by clicking Run in the Visual Studio Test Explorer.
Luckily, I've found the great Docker.DotNet library which allows you to access the Docker Daemon directly in your C# code, e.g. during test setup and teardown methods. The integration & setup for such integration tests is then pretty straightforward, but requires a bit of code to get it reliably working. Just follow the snippets below and you should be able to set up something similar!
The class DockerSqlDatabaseUtilities is where Docker.DotNet is referenced - it's purpose is to ensure that a Microsoft SQL Server Docker image is running and ready for connections.
Next, SqlServerDockerCollectionFixture is a test fixture that essentially just orchestrates the Docker setup and implements XUnit's IAsyncLifetime interface.
Further down, we're specifying a custom TestFramework in AssemblyInfo.cs, since we'll be running the test setup from the previous fixture class only once per assembly, and that's not supported out of the box in XUnit. You could refactor the code to internally track if the setup has already been performed, or simply use the Xunit.Extensions.Ordering as test framework😀
Another class I'm using is TestHelper. You can guess from the name that it's offering supporting methods during testing, such as providing an in memory instance of our OpenID Service Dangl.Identity and performing the actual setup of our in memory ASP.NET Core backend, which is what we actually want to test.
Finally, we've got an abstract base class IntegrationTestBase that all our tests inherit from. TestHelper is actually provided as a dedicated instance per test, which means that we can run all tests in parallel yet still fully isolated from each other.
Does this approach work for you? Tell me in the comments!
Happy testing!