stryker-net-mutation
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.
Install with skills.sh (any agent)
npx skills add testland/qa --skill stryker-net-mutationstryker-net-mutation
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
Step 1 - Install
dotnet tool install -g dotnet-strykerPer-project (recommended for CI determinism):
dotnet new tool-manifest
dotnet tool install dotnet-strykerStep 2 - Run
# From the test project directory
cd MyApp.Tests
dotnet strykerThe 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:
Step 4 - Solution mode
For multi-project solutions:
dotnet stryker --solution path/to/MyApp.slnStryker.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.
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
References
Stryker.NET mutators and configuration reference
View source (opens in new window)Stryker.NET mutators and configuration reference
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:
| Mutator | Example |
|---|---|
| Arithmetic operator | + → -, * → / |
| Conditional boundary | < → <=, > → >= |
| Conditional negation | !x → x |
| Logical operator | && → || |
| Equality | == → != |
| Return value | return foo() → return null / return "" / return 0 |
| Statement removal | Foo(); → ; |
| String literal | "x" → "" |
mutation-level controls how many of these run:
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.
Related skills
mull-mutation
Runs Mull, the LLVM-IR mutation testing tool, against C/C++ test binaries built with Clang: covers install (the version-matched mull-NN package), the -fpass-plugin build flags for the Mull IR frontend, mull-runner invocation, the mutator catalog, path filtering, and GitHub Actions CI. Use when a C or C++ project needs mutation-score verification with the tool already chosen. Does not select among mutation tools and does not cover other languages (stryker-mutation for JS/TS, stryker-net-mutation for .NET, pitest-mutation for the JVM, mutmut-mutation for Python).
mutant-survival-triage
Normalizes a surviving-mutant record across StrykerJS, PIT, mutmut, and Mull into one shape, classifies why it survived (missing case, weak assertion, equivalent mutant, unreachable code, flaky killer), applies per-mutator heuristics for conditional-boundary, arithmetic-operator, statement-removal, and constant mutations, and drafts the specific test that would kill it. Treats equivalence as a judgment call, because deciding whether a mutant is equivalent to the original is undecidable in general, so a residual survivor rate is expected rather than a defect. Use when a mutation run has finished and the report lists surviving mutants that nobody has yet explained or turned into concrete test cases.
mutmut-mutation
Configures mutmut for Python mutation testing - `pip install mutmut`, runs via `mutmut run`, browses results via `mutmut browse` or `mutmut results`, applies surviving mutants to disk via `mutmut apply {id}`, suppresses with `# pragma: no mutate` annotations. Configures via `setup.cfg` / `pyproject.toml` with `source_paths` + per-test selection. Use for Python codebases needing mutation-quality verification of pytest / unittest suites.
pitest-mutation
Configures PIT (PITest) for mutation testing of JVM projects (Java, Kotlin via the Kotlin plugin) - wires the `pitest-maven` or `pitest-gradle-plugin` with `mutationThreshold`, `coverageThreshold`, target classes/tests filtering, runs `mvn pitest:mutationCoverage`, parses the HTML + XML reports. Use when the JVM suite needs mutation-quality verification - the canonical Java mutation testing tool, fast (PIT analyzes "in minutes rather than days").
stryker-mutation
Configures StrykerJS for mutation testing of JavaScript / TypeScript / React / Vue / Svelte / Node - picks the test-runner plugin (`@stryker-mutator/jest-runner`, `mocha-runner`, `vitest-runner`, `karma-runner`), authors `stryker.conf.json` with mutate globs + thresholds, runs incremental mode for PRs (only mutate changed files), and reports the mutation score. Use when a JS/TS test suite has ≥80% line coverage and the team wants to verify the tests actually catch bugs (not just touch lines).