Minitest Integration with Mergify
Report your test results from Minitest to Mergify
This guide explains how to integrate Minitest
with Test Engine using the minitest-mergify gem, a Minitest plugin. The
plugin uploads your test results to Test Engine when the run ends, with no
JUnit file to write or upload, and applies
quarantine and flaky detection
inside the Minitest run. Rails test suites run on Minitest, so this page
covers them too.
If your CI uploads a JUnit report from minitest-junit or
minitest-reporters today, see
Moving from a JUnit upload.
Requirements
Section titled Requirements-
CRuby 3.1 to 4.0. JRuby, TruffleRuby, development builds of Ruby (
ruby-head) and jobs that install gems with Bundler’sforce_ruby_platformare not supported: the suite runs, but nothing reaches Test Engine, and the plugin says why after the run. Use a JUnit report upload for those jobs, withminitest-junitorminitest-reporterswriting the report. -
Ruby 3.1 or later with Minitest 5.19 or later, or Ruby 3.2 or later with Minitest 6.
-
Linux on x86_64 or aarch64 (glibc 2.30 or newer, or musl), macOS, or Windows on x86_64. The gem runs on
mergify-ci-core, precompiled for those platforms, which Bundler installs alongside it at the same version. On any other platform your suite still runs, but nothing reaches Test Engine and the plugin says why after Minitest’s summary: use a JUnit report upload there too. -
If your gems come from a mirror that only serves an allowlist, allow
mergify-ci-coreas well asminitest-mergify.
The plugin reports tests written in classes and in describe blocks, whether
Minitest runs them in one process or in parallel, in its own threads
(parallelize_me!) or in Rails’ parallel workers (parallelize).
Installation
Section titled InstallationAdd the minitest-mergify gem
to the test group of your Gemfile:
group :test do gem 'minitest-mergify'endThen run:
bundle installUse the Bundler version your Gemfile.lock was written with (its
BUNDLED WITH line), or Bundler rewrites that line.
Require the plugin
Section titled Require the pluginRequire the plugin where your suite sets Minitest up, before its tests are
defined, such as in test/test_helper.rb:
require 'minitest/mergify'Minitest 6 loads only the plugins a suite asks for, so without this line
nothing reaches Test Engine. Minitest 5 loads the plugin by itself, but only
once the tests are defined: keep the line there too, or a spec test with no
body (it "does a thing" without a block) is reported without its line
number, under the file of another test in the same describe.
The plugin works alongside the reporters you already use, minitest-reporters
included. It prints what it did after Minitest’s summary, under a
# Mergify CI: heading.
Running in CI
Section titled Running in CIWhen the plugin reports
Section titled When the plugin reportsThe plugin uploads results only when the CI environment variable is set, or
when MERGIFY_CI_ENABLE is on. GitHub Actions, CircleCI and Buildkite set CI
for you. Jenkins does not: set CI=true, or MERGIFY_CI_ENABLE=true, in
the stage that runs your tests. If your tests behave differently when CI is
set (some test suites skip or add tests on CI), set MERGIFY_CI_ENABLE=true
instead of CI, so the tests that run stay the same.
CI counts as set unless it is empty or a false value (false, no, off,
0, f or n), so true, 1 and a CI system’s name (Woodpecker sets CI
to woodpecker) all count. MERGIFY_CI_ENABLE is stricter: only true,
yes, on, 1, t or y turn it on. Case does not matter for either.
Without MERGIFY_TOKEN the plugin uploads nothing and your tests run as
usual. Pull requests from forks and Dependabot pull requests on GitHub Actions
get no repository secrets, so they report nothing: no guard is needed in the
workflow. (The RSpec gem logs an authentication error when the variable is
set but empty, as on those pull requests; the tests are not affected.) To have
Dependabot pull requests report too, store the key as a Dependabot secret as
well (gh secret set MERGIFY_TOKEN --app dependabot --repo <owner>/<repository>).
Job names
Section titled Job namesEvery test is identified by its name and the name of the CI job that ran it. By default the job name is the job’s identifier in your CI:
| CI | Default job name |
|---|---|
| GitHub Actions | the job id (GITHUB_JOB), recorded with the workflow name (GITHUB_WORKFLOW) |
| CircleCI | CIRCLE_JOB, the expanded job name of each matrix leg |
| Buildkite | the step label (BUILDKITE_LABEL) |
| Jenkins | JOB_NAME, also used as the pipeline name |
Set MERGIFY_TEST_JOB_NAME when that default is not one stable name per set of
tests:
-
GitHub Actions matrix: every leg shares one job id. Set it per leg from the matrix values, for example
MERGIFY_TEST_JOB_NAME: test (${{ matrix.python-version }}, ${{ matrix.os }}). -
Two test runs in one job: set a distinct name on each step.
-
Reusable workflows: a called workflow reports under the caller’s workflow name, so two called workflows whose jobs share an id (often
test) collide. Set a distinct name in each. -
Jenkins multibranch pipelines: each branch and each pull request keeps its own test history, and no setting changes that.
JOB_NAMEincludes the branch or pull request (my-repo/PR-123) and is also the pipeline name tests are filed under;MERGIFY_TEST_JOB_NAMEreplaces only the job name. Do not set it for this purpose. -
Shards of one test suite are the same tests split across machines: give all shards the same name. Never put a shard index in it.
Tests inside tox, Docker or Docker Compose
Section titled Tests inside tox, Docker or Docker Composetox, docker run and Docker Compose start the tests in an environment that
does not inherit the CI job’s variables. Pass through MERGIFY_TOKEN, CI,
MERGIFY_TEST_JOB_NAME if you set it, and every variable the plugin reads to
identify the run. tox’s pass_env accepts globs (GITHUB_*); Docker Compose,
docker run -e and the Buildkite Docker Compose plugin need each name:
GitHub Actions: GITHUB_ACTIONS GITHUB_BASE_REF GITHUB_EVENT_NAME GITHUB_EVENT_PATH GITHUB_HEAD_REF GITHUB_JOB GITHUB_REF_NAME GITHUB_REF_TYPE GITHUB_REPOSITORY GITHUB_REPOSITORY_ID GITHUB_RUN_ATTEMPT GITHUB_RUN_ID GITHUB_SERVER_URL GITHUB_SHA GITHUB_WORKFLOW RUNNER_NAMECircleCI: CIRCLECI CIRCLE_BRANCH CIRCLE_BUILD_URL CIRCLE_JOB CIRCLE_REPOSITORY_URL CIRCLE_SHA1 CIRCLE_WORKFLOW_IDBuildkite: BUILDKITE BUILDKITE_AGENT_NAME BUILDKITE_BRANCH BUILDKITE_BUILD_ID BUILDKITE_BUILD_URL BUILDKITE_COMMIT BUILDKITE_LABEL BUILDKITE_PIPELINE_SLUG BUILDKITE_PULL_REQUEST BUILDKITE_PULL_REQUEST_BASE_BRANCH BUILDKITE_REPO BUILDKITE_RETRY_COUNT BUILDKITE_STEP_KEYJenkins: JENKINS_URL BUILD_ID BUILD_URL JOB_NAME NODE_NAME GIT_URL GIT_BRANCH GIT_COMMIT CHANGE_TARGETPass them by name, never by writing a value into a file. In a container on
GitHub Actions, also mount the file GITHUB_EVENT_PATH points to, at the same
path: without it the run is recorded against the merge commit instead of the
pull request’s head commit.
Workflow linters
Section titled Workflow lintersDepending on its version and persona, zizmor can
report secrets-outside-env on the MERGIFY_TOKEN: ${{ secrets.MERGIFY_TOKEN }}
line. Run the repository’s own zizmor configuration on the changed workflow,
and if it reports the finding, follow the convention the repository already
uses for it: a deployment environment on the job, or an inline
# zizmor: ignore[secrets-outside-env] comment.
Update your CI workflow
Section titled Update your CI workflowYour workflow should run your tests as usual while exporting the secret
MERGIFY_TOKEN as an environment variable.
GitHub Actions
Section titled GitHub ActionsAdd the following to the GitHub Actions step running your tests:
env: MERGIFY_TOKEN: ${{ secrets.MERGIFY_TOKEN }}For example:
- name: Run Minitest tests env: MERGIFY_TOKEN: ${{ secrets.MERGIFY_TOKEN }} run: bundle exec rake testIn a Rails application, keep the command you use today, such as
bin/rails test.
Buildkite
Section titled BuildkiteSet MERGIFY_TOKEN in the environment of the agents running your tests.
The step itself then needs no Mergify-specific configuration:
steps: - label: "Run Minitest tests" command: bundle exec rake testThe plugin collects your test results and sends them to Test Engine.
Quarantine and crashed runs
Section titled Quarantine and crashed runsBefore any test runs, the plugin fetches the list of tests
quarantined in Test Engine. A quarantined test
still runs and reaches Test Engine with its real result, a failure included,
but that failure does not fail the run: Minitest counts it as a skip, and so
do your other reporters. Minitest’s verbose output (--verbose) gives the
skip’s reason, Test is quarantined from Mergify Test Insights. This works
the same whether your tests run in one process or in parallel.
A test quarantined while your repository uploaded JUnit reports stays
quarantined: the plugin also matches each test under the name
minitest-junit and minitest-reporters gave it, such as
UserTest.test_valid.
The exit code of your test step already accounts for quarantine, so the step needs nothing more:
-
Do not add
continue-on-error: true. The recipes that upload a JUnit report with themergifyio/gha-mergify-ciaction need it because the action decides the job’s result after the tests. Here nothing does, so it would let every real failure through. -
There is no step
idto set and notest_step_outcometo pass: both belong to that action, which this setup does not use.
A crash cannot pass for a green run either. Quarantine only covers the failure of a test: a test file that fails to load, or Minitest dying mid-run, still exits non-zero and fails the step. The plugin uploads results when the run ends, so a process killed outright before then, such as by the out-of-memory killer, sends nothing to Test Engine.
If the plugin cannot fetch the quarantine list, it quarantines nothing for that run, and a quarantined test that fails makes the step fail as usual.
Flaky detection
Section titled Flaky detectionThe plugin reruns tests within the budgets set under Native Flaky Detection:
-
On a pull request, a test the target branch does not have yet is new. Once the suite is over, the plugin reruns the new tests within the Prevention Budget. If one of those runs fails, the test is flaky and the job fails, so the pull request does not merge. See Prevention.
-
On a branch with no target, such as a push to your default branch, the plugin reruns the tests Test Engine knows as unhealthy, within the Detection Budget, to report whether they still are. The job’s result does not change. See Rerunning unhealthy tests.
The reruns go through Minitest itself, so a test that ran in Minitest’s threads
or in Rails’ parallel workers is rerun there. Minitest’s summary and your other
reporters show only your run, so a new test the reruns found flaky still counts
as passed there. The exit code and the plugin’s # Mergify CI: section say
otherwise.
Test names
Section titled Test namesA test in a class is named as Minitest names it:
UserTest#test_validA test in a describe block is named by its file (relative to the directory
Minitest runs in), the describe blocks around it and its text:
test/models/user_test.rb > User > when banned > cannot log inMinitest numbers each it by its position in its describe
(test_0001_cannot log in), so adding a test would rename every one below it.
The name leaves that number out, except when two tests in the same describe
share their text: those keep Minitest’s number, so they stay apart.
Test Engine tracks a test’s history under its name. For a test in a class,
renaming the class or the method starts a new test. For a test in a describe
block, renaming its file, a describe block or its text does. A test that
reached Test Engine through a JUnit upload, as UserTest.test_valid, starts
its history again under its new name, while its
quarantine entry still applies.
Matrix runs
Section titled Matrix runsThe plugin uploads each run’s results under your CI job’s name. On GitHub
Actions, that name is the job’s identifier (GITHUB_JOB, recorded with the
workflow name), which every leg of a matrix shares. The job name is
part of a test’s identity
in Test Engine, so when the legs run the same suite on several Ruby versions,
their results merge into one test.
Set MERGIFY_TEST_JOB_NAME on each leg and include the matrix value, so each
leg reports under its own name:
jobs: test: runs-on: ubuntu-latest strategy: matrix: ruby-version: ['3.3', '3.4'] steps: # ... - name: Run Minitest tests env: MERGIFY_TOKEN: ${{ secrets.MERGIFY_TOKEN }} MERGIFY_TEST_JOB_NAME: test (ruby ${{ matrix.ruby-version }}) run: bundle exec rake testName each leg after the environment it runs in, never after a shard of the suite: a test can move to another shard when tests are added or removed, so a name per shard would split that test’s history.
Setting MERGIFY_TEST_JOB_NAME on a job that already reports changes its
name, so its tests start a new history in Test Engine.
Moving from a JUnit upload
Section titled Moving from a JUnit uploadIf your CI writes a JUnit report with minitest-junit or minitest-reporters
and uploads it with the mergifyio/gha-mergify-ci action, the
mergifyio/mergify-ci Buildkite plugin or mergify ci junit-process, switch
to minitest-mergify:
-
Stop writing the JUnit report, unless another tool still reads the file:
-
With
minitest-junit, remove the gem from yourGemfileand therequire 'minitest/junit'line from your test helper. Then remove its options (-rminitest/junit,--junit,--junit-filename) and theJUNIT_OUTPUT_DIRvariable wherever you set them: the test command, a Rake task or the test step’senv. -
With
minitest-reporters, removeMinitest::Reporters::JUnitReporterfrom yourMinitest::Reporters.use!list. Keep the gem if you use its other reporters: the plugin works alongside them. Otherwise, also remove the gem, therequire 'minitest/reporters'line and theMinitest::Reporters.use!call.
-
-
Remove the upload: the
mergifyio/gha-mergify-cistep, themergifyio/mergify-ciplugin on the Buildkite step, or themergify ci junit-processcommand.minitest-mergifysends the results itself. If you ranmergify ci junit-process, also drop|| exit_code=$?from your test command, so its exit code is the step’s again. -
On GitHub Actions, remove
continue-on-error: truefrom the test step. If you added a step for pull requests without the secret that fails the job when no report was uploaded, remove it too. -
If your upload set a
job_name, as a matrix setup does, move its value toMERGIFY_TEST_JOB_NAMEon the step that runs the tests, as shown in Matrix runs, so your tests keep the job name they report under. -
Install
minitest-mergifyand require it, as shown in Installation, and setMERGIFY_TOKENon the step that runs the tests, as shown in Update your CI workflow.
What changes in Test Engine once the plugin uploads:
-
Quarantine entries made from your JUnit uploads still apply: the plugin also matches each test under the name the upload gave it, such as
UserTest.test_valid. -
Your tests get new names, so their history starts again under those names.
Verify and review in Test Engine
Section titled Verify and review in Test EngineAfter pushing these changes, your next CI run reports its Minitest results automatically.
You can then review your test results, including any failures or flaky tests, directly in the Test Engine dashboard.
Environment variables
Section titled Environment variables| Variable | Purpose | Default |
|---|---|---|
MERGIFY_API_URL |
Base URL of the Mergify API. An API URL given on the command line or in a reporter's configuration takes precedence. | https://api.mergify.com |
MERGIFY_CI_DEBUG |
Set to | false |
MERGIFY_CI_ENABLE |
Set to | false |
MERGIFY_TEST_JOB_NAME |
Name the job's test results are reported under, instead of the CI job's own name. Set one per environment, never per shard: a name per shard splits a test's history. | |
MERGIFY_TEST_RETRY_ENABLE |
Set to | true |
MERGIFY_TOKEN |
The Mergify credential: an application key in CI. Without it the test reporters upload nothing. A | |
MERGIFY_TRACEPARENT |
W3C |
To check the setup without uploading anything, run your tests with
MERGIFY_CI_ENABLE=true MERGIFY_CI_DEBUG=true: the plugin prints the spans it
would send.
Was this page helpful?
Thanks for your feedback!