---
title: "Running muttest in CI"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Running muttest in CI}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>"
)
```

Running mutation tests in CI gives you a persistent, objective record of test quality that evolves alongside your codebase. This guide covers GitHub Actions setup, badge reporting, and practical tips for keeping CI runs fast.

## Why run mutation tests in CI

- **Regression detection** — a drop in mutation score on a PR signals that new code shipped with weak tests.
- **Visible accountability** — a badge in the README makes test quality a first-class metric alongside coverage.
- **Automated baseline** — no one has to remember to run `muttest` locally; the score is always up to date.

## GitHub Actions: minimal workflow

Create `.github/workflows/test-mutation.yaml`:

```yaml
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

name: Mutation Testing

jobs:
  mutation-test:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - uses: r-lib/actions/setup-r@v2
        with:
          use-public-rspm: true

      - uses: r-lib/actions/setup-r-dependencies@v2
        with:
          extra-packages: |
            jakubsob/muttest

      - name: Run mutation tests
        shell: Rscript {0}
        run: |
          plan <- muttest::muttest_plan(c(
            muttest::arithmetic_operators(),
            muttest::comparison_operators()
          ))
          muttest::muttest(plan)
```

Adjust `source_files` and `mutators` to match your package.

## Mutation score badge

The `muttest` repository ships a badge workflow that writes a JSON endpoint to a `badges` branch and renders it as a shield. See the [badge workflow](https://github.com/jakubsob/muttest/blob/main/.github/workflows/test-mutation.yaml) in the `muttest` repository for the full implementation pattern.

The badge in your README looks like:

```markdown
[![muttest](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/YOUR_ORG/YOUR_REPO/badges/badges/muttest.json)](https://github.com/YOUR_ORG/YOUR_REPO/actions/workflows/test-mutation.yaml)
```

## Choosing which files to mutate in CI

Mutation testing multiplies your test runtime: running N mutants means running your test suite N times. Keep CI fast by being selective:

**Mutate:**

- Files with complex branching or arithmetic logic
- Critical domain code (validation, calculations, filters)
- Recently changed files (use `git diff` to scope a PR run)

**Skip:**

- Pure utility files with trivial logic
- Auto-generated code
- Files already tested by slow integration tests (run those separately)

A practical approach: define a narrow `source_files` list in your CI plan covering only the high-value files, and run the broader set locally or on a nightly schedule.

## Failing the build on a low score

To enforce a minimum mutation score, parse the output or use R's exit codes:

```r
score <- muttest::muttest(plan)
if (score < 0.7) {
  message(sprintf("Mutation score %.1f%% is below threshold 70%%", score * 100))
  quit(status = 1)
}
```

Start with a threshold that reflects your current score, then tighten it over time as you improve your tests.

## Performance tips

- **Scope `source_files` tightly** — the fewer files, the fewer mutants, the faster the run.
- **Use fast unit tests** — mutation testing re-runs your test suite for each mutant. If individual tests are slow, the total time scales badly. Prefer tests that mock external dependencies.
- **Run on PRs to changed files only** — scope the mutation plan to files touched by the PR using `git diff --name-only origin/main...HEAD`.
