stryker-net-mutation
Overview
Stryker.NET brings mutation testing to .NET Core and .NET Framework projects,
distributed as the dotnet-stryker global tool (introduction).
When to use
- A .NET test suite (xUnit / NUnit / MSTest) needs mutation-quality
verification.
- A C# library needs higher confidence than coverage alone shows.
- The team wants Stryker-family parity across JS + .NET.
Step 1 - Install
dotnet tool install -g dotnet-stryker
Per-project (recommended for CI determinism):
dotnet new tool-manifest
dotnet tool install dotnet-stryker
Step 2 - Run
# From the test project directory
cd MyApp.Tests
dotnet stryker
The first run discovers the test framework, reports baseline
coverage, then mutates and re-tests.
Step 3 - Configure via stryker-config.json
Per stryker-net-config, every option nests under a single
stryker-config root object; Stryker.NET publishes no JSON schema, so
there is no $schema key to reference.
{
"stryker-config": {
"project": "../MyApp/MyApp.csproj",
"test-projects": ["MyApp.Tests.csproj"],
"mutation-level": "Standard",
"thresholds": { "high": 80, "low": 60, "break": 50 },
"concurrency": 4,
"reporters": ["progress", "html", "cleartext"]
}
}
mutation-level controls how aggressive mutations are:
Basic (fewest mutators)
Standard (default, recommended)
Advanced
Complete (most mutators; slowest)
Step 4 - Solution mode
For multi-project solutions:
dotnet stryker --solution path/to/MyApp.sln
Stryker.NET discovers all test projects and mutates the production
projects each test references.
Step 5 - CI integration
- uses: actions/setup-dotnet@v4
with: { dotnet-version: '8.x' }
- run: dotnet tool restore
- run: dotnet stryker --break-at 50
- uses: actions/upload-artifact@v4
if: always()
with:
name: stryker-net-report
path: StrykerOutput/
Reports land under StrykerOutput/<timestamp>/reports/mutation-report.html.
Step 6 - Mutators (per Standard level)
Standard-level mutators cover arithmetic and logical operators, equality,
conditional boundary and negation, return-value, statement removal, and string
literal. A surviving mutant means the test suite doesn't distinguish the original
behavior from the mutated one. Example - conditional boundary:
| Mutator | Example |
|---|
| Conditional boundary | < → <=, > → >= |
The full mutator table and the stryker-config.json option list are in
references/stryker-net-mutators.md.
Worked example
MyApp ships a PricingService; MyApp.Tests uses xUnit and is green.
dotnet tool install -g dotnet-stryker, then from MyApp.Tests run dotnet stryker once to confirm xUnit discovery.
- Add
stryker-config.json pointing project at ../MyApp/MyApp.csproj, mutation-level Standard, thresholds { "high": 80, "low": 60, "break": 50 }.
dotnet stryker writes StrykerOutput/<timestamp>/reports/mutation-report.html, which flags a surviving conditional-boundary mutant: qty > 0 mutated to qty >= 0.
- Add an xUnit test at
qty == 0; re-running dotnet stryker kills the mutant and lifts the score above the break threshold.
- In CI, run
dotnet stryker --break-at 50 and upload StrykerOutput/.
Anti-patterns
| Anti-pattern | Why it fails | Fix |
|---|
| Running on the entire solution every PR | Long runtime; team disables. | Scope to changed projects via --project. |
mutation-level: Complete from day one | Slowest mode; many irrelevant mutants. | Start Standard; promote to Complete for critical libraries. |
| Ignoring "no coverage" mutants in the report | Untested code; mutation testing can't measure it. | Add tests OR exclude those files via --mutate. |
Skipping --break-at in CI | Mutation score regressions slip through. | Set --break-at to baseline + small headroom (Step 5). |
| Running against a Debug build | Slower; some mutators behave differently. | Run against Release build for CI gates. |
Limitations
- Newer than StrykerJS. Per stryker-net-intro, the
.NET variant filled "what developers perceived as a gap";
feature parity with JS is improving but not 1:1.
- Async + concurrency mutation can be tricky. Some mutants
produce equivalent code; flag + exclude.
- CI runtime. A medium codebase mutation run takes 10-60 min;
not for every PR.
References
- sni - Stryker.NET overview, NuGet distribution, .NET
Core + .NET Framework support, history.
- snc -
stryker-config.json / .yaml file format and the
full option list.
stryker-mutation - JS sibling
with the same Stryker model.
pitest-mutation,
mutmut-mutation,
mull-mutation - per-language
alternatives.