Configures Stryker.NET for mutation testing of .NET Core / .NET Framework projects - installs `dotnet-stryker` global tool, scopes mutation to specific csproj, supports xUnit / NUnit / MSTest, authors `stryker-config.json` with thresholds, runs in CI. Use when a .NET test suite needs mutation-quality verification - closes the .NET ecosystem gap left by Stryker.NET being newer than the JS variant.
80
100%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Stryker.NET brings mutation testing to .NET Core and .NET Framework projects,
distributed as the dotnet-stryker global tool (introduction).
dotnet tool install -g dotnet-strykerPer-project (recommended for CI determinism):
dotnet new tool-manifest
dotnet tool install dotnet-stryker# From the test project directory
cd MyApp.Tests
dotnet strykerThe first run discovers the test framework, reports baseline coverage, then mutates and re-tests.
stryker-config.jsonPer 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)AdvancedComplete (most mutators; slowest)For multi-project solutions:
dotnet stryker --solution path/to/MyApp.slnStryker.NET discovers all test projects and mutates the production projects each test references.
- 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.
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.
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.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.qty == 0; re-running dotnet stryker kills the mutant and lifts the score above the break threshold.dotnet stryker --break-at 50 and upload StrykerOutput/.| 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. |
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.