ℹ English is not my native language and i'm not using any AI to write this article. Links to code will be added so you can follow the whole process through the articles, do not hesitate to click links
Never have you ever debugged an integration test, found that it has failed, and unfortunately pressed the red squared button to kill the debugging, skipping the whole teardown logic?
Tell me you've never seen an integration test CI starting your test and for some reason, be stopped unexpectedly ? (Timeout, OOM, Job canceled).
That's why TestContainers has made Ryuk, a docker container alongside others that clean them after your tests.
It works absolutely wonderfully because it does not rely on the test process to clean your environment.
But i had one problem.
Docker was not available for the infrastructure i wanted to set up.
And my dev server was not always cleaned by the test.
A lot of orphan test message bus subscriptions were left alone.
Making the dev server a complete mess of useless queues with large random guid names. Databases suffered from the same problem.
That's why i have been working on a watchdog that handles not only docker containers, but any infrastructure that is set up in your tests.
And it is integrated in NotoriousTest, an integration test framework made to simplify the making of clean, reusable integration tests.
DoggyDog has been made to work alongside NotoriousTest, all behaviour described here will be supported natively by NT without you doing anything.
But since we are here to talk about the watchdog, lets see how it works :
Doggydog, the nice and gentle dog that keeps tracks of your infrastructures.
DoggyDog is a self-contained executable, made with .NET 10.
It has the responsibility to clean all your orphan infrastructures after the test process quits without success.
A well trained dog that waits for your test to crash
🐶 DoggyDog is well trained, he will sit gently before doing anything to your infrastructures. Don't worry, he will move only after the right signal is emitted.
You will need to provide a PID to the Watchdog.
He will wait until the process ends.
After the process ended, he will look for a signal, more specifically, a temporary signal file.
This file name is formatted with an "EnvironmentId" (nt-{EnvironmentId}.signal).
If he finds this file, it means that the process has exited correctly and had the time to clean the environment.
ℹ Why does DoggyDog not use exit code to ensure the test process exits correctly or not ?
On Windows, the exit code is available for everyone as a public information.
On Linux, however, this information is available only for the parent process. Which is not the case here since DoggyDog is finding the process with the PID.
For compatibility reasons, i needed to find an alternative.🐶 Then, the DoggyDog will leave and return to his Doghouse until you need him for another guard duty.
But what if he doesn't find his signal file ?
If the file is not present, DoggyDog will start the clean process.
How DoggyDog takes care of the mess after dinner
Loading the test assembly as a plugin
While tests were running, DoggyDog loaded the test assembly inside his default load context.
To do so, he takes a path to the test assembly and its runtimes to load them inside his default load context.
You need to think of the test assembly as a plugin of DoggyDog.
You define classes with predefined interfaces and attributes, and DoggyDog will find them later to do his job.
DoggyDog takes runtimes as well to resolve ASP.NET Core assemblies and/or .NET assemblies that could be referenced inside the test projects. (e.g. Microsoft.* or System.* packages).
ℹ Why not using an Assembly Load Context ?
DoggyDog must be seen as a process "inside" the test process.
Later, we will see that DoggyDog uses a sqlite registry to retrieve infrastructure types, and loads them from the test assembly.
He will need to make comparisons between referenced types in DoggyDog and types in the test project.
Comparison between 2 instances of the same version but with different calling assemblies will not result in equality. I didn't want to have to design DoggyDog around full reflection.
Runtimes paths are needed for two reasons :
- Because DoggyDog is self contained. You don't need to have .NET 10 installed on your machine to run DoggyDog (NotoriousTest is trying to be as compatible as possible with .netstandard2.1, and when it's not possible, .NET 8). Therefore, he will not resolve framework types from the .net assembly.
- Because DoggyDog is a .NET executable, and does not reference ASP.NET Core. And i didn't want to make a direct reference to ASP.NET to handle assembly resolution.
Retrieving orphan infrastructures and their cleaners
The test process will populate a sqlite registry when it initializes an infrastructure with InfrastructureRegistryEntry:
- EnvironmentId : An ID that identifies a subset of infrastructures associated with a test campaign.
- InfrastructureId
- InfrastructureType
- Metadata and MetadataType : Necessary information to clean the infrastructure (such as connection strings, container name/id, subscription name, etc...)
For every infrastructure that has not been deleted from the test process, DoggyDog will :
- Load the infrastructure type.
- Retrieve the CleanerAttribute associated with the infrastructure class.
- Retrieve the IInfrastructureCleaner in the CleanerType property of the attribute.
- Instantiate the Cleaner as an IInfrastructureCleaner.
- Call the method CleanAfterCrash with the metadata object loaded from the registry, serialized as the MetadataType.
- Remove the infrastructure from the registry.
By adding on your infrastructure classes the cleaner attribute, and implementing an IInfrastructureCleaner, you can clean any infrastructure that you want, even if it's a docker container, a real database, a file.
Infrastructure events
DoggyDog is always watching the registry.
When the test project creates an infrastructure in the registry, adds a reset date, or deletes an infrastructure, DoggyDog will see it and log into the console what happened.
The End
In conclusion, DoggyDog is a watchdog that waits for your test process to stop, looks in a sqlite registry for all infrastructures that have been created, and calls their associated cleaner, while notifying what happened to them.
It was really fun to get through all this reflection stuff.
I had never had the chance to dive into it and making everything work makes me genuinely proud.
Do not hesitate to share your thoughts about it, go through the repository , the docs and samples.
And to star the repo if you like it !
Have a great day !
🐶 Woof !




Top comments (0)