stryker-net-mutation

Use when a .NET (C#) test suite needs mutation testing to show whether the tests catch bugs. Sets up Stryker.NET: the dotnet-stryker tool, project scoping, xUnit, NUnit or MSTest, stryker-config.json thresholds and CI runs.

/plugin install qa-mutation-testing@testland-qa

First time in Claude Code? Add the marketplace with /plugin marketplace add testland/qa

View source (opens in a new tab)

Overview

Stryker.NET brings mutation testing to .NET Core and .NET Framework projects, distributed as the dotnet-stryker global tool (introduction (opens in new window)).

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 (opens in new window), 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:

MutatorExample
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.

  1. dotnet tool install -g dotnet-stryker, then from MyApp.Tests run dotnet stryker once to confirm xUnit discovery.
  2. Add stryker-config.json pointing project at ../MyApp/MyApp.csproj, mutation-level Standard, thresholds { "high": 80, "low": 60, "break": 50 }.
  3. dotnet stryker writes StrykerOutput/<timestamp>/reports/mutation-report.html, which flags a surviving conditional-boundary mutant: qty > 0 mutated to qty >= 0.
  4. Add an xUnit test at qty == 0; re-running dotnet stryker kills the mutant and lifts the score above the break threshold.
  5. In CI, run dotnet stryker --break-at 50 and upload StrykerOutput/.

Anti-patterns

Anti-patternWhy it failsFix
Running on the entire solution every PRLong runtime; team disables.Scope to changed projects via --project.
mutation-level: Complete from day oneSlowest mode; many irrelevant mutants.Start Standard; promote to Complete for critical libraries.
Ignoring "no coverage" mutants in the reportUntested code; mutation testing can't measure it.Add tests OR exclude those files via --mutate.
Skipping --break-at in CIMutation score regressions slip through.Set --break-at to baseline + small headroom (Step 5).
Running against a Debug buildSlower; some mutators behave differently.Run against Release build for CI gates.

Limitations

  • Newer than StrykerJS. Per stryker-net-intro (opens in new window), 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 (opens in new window) - Stryker.NET overview, NuGet distribution, .NET Core + .NET Framework support, history.
  • snc (opens in new window) - 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.
Reference

Stryker.NET mutators and configuration reference

View source (opens in a new tab) (opens in new window)

Full mutator set and configuration options for stryker-net-mutation. Linked inline from that skill's SKILL.md, which keeps the conditional-boundary example in the spine.

Mutators at the Standard level

A surviving mutant means the test suite doesn't distinguish the original behavior from the mutated one. Common mutators:

MutatorExample
Arithmetic operator+ → -, * → /
Conditional boundary< → <=, > → >=
Conditional negation!x → x
Logical operator&& → ||
Equality== → !=
Return valuereturn foo() → return null / return "" / return 0
Statement removalFoo(); → ;
String literal"x" → ""

mutation-level controls how many of these run:

  • Basic (fewest mutators)
  • Standard (default, recommended)
  • Advanced
  • Complete (most mutators; slowest)

Configuration options

Per stryker-net-config (opens in new window), every option nests under a single stryker-config root object; Stryker.NET publishes no JSON schema, so there is no $schema key. Core keys: project, test-projects, mutation-level, thresholds (high / low / break), concurrency, reporters. Command-line flags mirror them, for example --solution (multi-project discovery), --project, --mutate (scope / exclude files), and --break-at <baseline> for the CI gate.