How to properly unit test a .NET project with multiple target frameworks, given implementation differences among targets?
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
Multi-targeted .NET libraries are tricky to test because the public API may stay stable while implementation details legitimately differ per target framework. A good test strategy verifies common behavior once, then adds targeted tests only where framework-specific differences are real and intentional. The goal is not to duplicate every test for every target without thought, but to structure the test project so differences are explicit and maintainable.
Start From A Multi-Targeted Test Project
The simplest baseline is to multi-target the test project too, so the same tests run against each framework target.
This ensures each target compiles and executes against its own implementation path.
Separate Shared Contract Tests From Framework-Specific Tests
Most tests should assert behavior that must stay identical across all targets. Keep those in a normal shared test class.
Then isolate target-specific expectations only where behavior actually differs.
Use Conditional Compilation Sparingly
If implementation differences are intentional, you can branch tests with target symbols.
This works, but it can become messy if overused. Prefer separate test files or helpers when differences grow beyond a few lines.
A Cleaner Pattern: Abstract Test Base Plus Target Helpers
For more complex differences, put shared assertions in a base class and provide target-specific expectations through helper properties or methods.
This keeps most logic shared and makes the difference visible in one place.
Run CI Per Target, Not Just Local Defaults
A multi-targeted test project is only useful if CI actually runs every target. Make the pipeline fail when one target passes and another breaks.
Use commands like:
Even if dotnet test runs all targets by default in your environment, explicit per-target jobs make failures easier to diagnose.
Test Behavior, Not Framework Internals
A common mistake is asserting specific internal implementation details per target instead of the public contract. If the library exposes the same method signature and intended behavior, tests should focus there first.
Only add framework-specific assertions when the difference is unavoidable, documented, and user-visible.
Common Pitfalls
- Copying the same test file into separate target-specific projects and creating drift.
- Using
#ifeverywhere until tests become harder to read than production code. - Testing internal mechanics instead of stable public behavior.
- Running CI on only one framework and assuming the others are safe.
- Leaving framework-specific differences undocumented, so failures look random.
Summary
- Multi-target the test project so each framework runs real tests against its own implementation.
- Keep most tests shared and behavior-focused.
- Isolate intentional target differences in small, explicit places.
- Use conditional compilation carefully and only when needed.
- Make CI execute and report each target separately.

