If you've ever had to test two different versions of the same interface, you know the pain. Writing the same test logic twice feels wrong, but copying test files creates a maintenance nightmare. There's a cleaner way.
The technique uses environment variables and vstest.console to run identical test cases against different implementations—without creating duplicate test files or complex project structures.
The Core Idea
You have one interface with two (or more) concrete implementations, and you want to verify they behave identically without duplicating your test code.
The solution: keep one test project, but run it twice. Each run targets a different implementation by switching what's registered in your IoC container.
Call vstest.console twice from a batch file, setting a different environment variable before each call. Inside your test assembly's initialization, that variable determines which implementation gets wired up.
Say you're testing a string-length calculator with a GetLength method. One implementation calls input.Count(), another calls input.Length. Both should return the same result, so you write one test method and let the setup decide which class answers.
How It Works
In your test project's AssemblyInitialize, read an environment variable (let's call it TestConfiguration). Based on that value, register the appropriate implementation in your IoC container. Run your test DLL once with TestConfiguration=1, then again with TestConfiguration=2.
The test code itself never changes. It asks the container for IFoo and receives whichever implementation the current run has registered.
For a handful of interfaces, straightforward if/else registration works fine. Once the number grows, switch to IoC Registration by Convention—a pattern where your container automatically discovers and registers types based on naming rules or configuration files.
Alternative: Multiple Project Files
Create separate test project files that reference the same test class files as linked items. Each project references only its target implementation assembly, generating multiple test DLLs from one set of source files.
This method lets you add implementation-specific tests alongside the shared ones. The trade-off is managing extra .csproj files.
Who Should Care
| Approach | Best For | Trade-off |
|---|---|---|
| Environment-variable technique | Teams wanting minimal project overhead, shared test suites | Less flexibility for per-implementation tests |
| Multiple .csproj files | Projects needing implementation-specific coverage | More project files to maintain |
Our Analysis
Test duplication is a silent maintenance tax. When a test changes, you update it in one place or risk divergence. The environment-variable approach keeps shared tests in a single location.
It slots cleanly into CI/CD pipelines. Batch files calling msbuild and vstest.console work with most build servers without custom tooling.
The convention registration angle matters if your codebase has many interfaces. Without it, AssemblyInitialize becomes a sprawling if/else chain—defeating the DRY benefit you're chasing.
The linked-files approach suits teams that need per-implementation test coverage alongside shared scenarios. If your implementations genuinely differ in behavior, separate projects give you room to express that.
Takeaways:
- One test project can test multiple implementations by switching IoC registrations based on an environment variable.
- Run vstest.console twice from a batch file, each with a different environment variable set.
- For many interfaces, use IoC Registration by Convention to avoid manual if/else chains in setup code.
- If you need implementation-specific tests alongside shared ones, multiple .csproj files with linked sources may fit better.
- This pattern requires no special tooling beyond a basic IoC container and command-line test execution.
Frequently asked questions
Do I need a specific testing framework to use this approach?
No. The technique works with any framework that runs via vstest.console, including MSTest, NUnit, and xUnit, as long as you can access environment variables in your test assembly initialization.
Can this handle more than two implementations?
Yes. Add more environment variable values and corresponding IoC registrations. For many values, switch to convention-based registration to keep setup manageable.
What if my implementations are in the same assembly as the interface?
The approach still works. You can register implementations conditionally or use a configuration file to control which classes get wired up during each test run.
Does this work in CI/CD pipelines?
Absolutely. Batch files calling msbuild and vstest.console integrate with most build servers. Some teams convert the logic to PowerShell or shell scripts for cross-platform compatibility.
What's the main risk with this technique?
Your test assembly initialization becomes a switching point. Keep the registration logic clean and consider moving it to a helper class to avoid bloating AssemblyInitialize with conditional logic.