cargo-nextest: Faster Rust Test Runner with Better Output

cargo-nextest: Faster Rust Test Runner with Better Output

cargo-nextest is a next-generation test runner for Rust that replaces cargo test with a process-per-test execution model, better terminal output, built-in retry logic, and significantly faster CI runs. It's a drop-in replacement — your existing tests work unchanged.

Why nextest?

cargo test runs tests in threads within a single process. This is fast but has drawbacks:

  • A test that leaks global state can corrupt later tests
  • Output from multiple tests interleaves in the terminal
  • No built-in retry for flaky tests
  • No per-test timeout enforcement

nextest runs each test in its own process. This costs some startup overhead but delivers:

  • Faster CI — nextest parallelizes more aggressively; typical speedup is 1.5-3× on large test suites
  • Clean isolation — process-per-test prevents state leaks between tests
  • Better output — live status per test, failure summary at the end
  • Retry logic — automatically retry flaky tests before declaring them failures
  • Per-test timeouts — kill tests that hang

Installation

cargo install cargo-nextest --locked

Or use the pre-built binary from the nextest releases page for faster CI setup.

In CI, use the official install action:

- uses: taiki-e/install-action@nextest

Basic Usage

Drop-in replacement for cargo test:

cargo nextest run          # replaces: cargo test
cargo nextest run --lib    <span class="hljs-comment"># lib tests only
cargo nextest run --<span class="hljs-built_in">test integration_test   <span class="hljs-comment"># specific test binary
cargo nextest run parsing  <span class="hljs-comment"># filter by test name

The output format is different from cargo test — nextest shows a live status indicator for each running test, then a summary of failures:

   Compiling my_crate v0.1.0
    Finished test profile [unoptimized + debuginfo] target(s) in 1.23s
    Starting 42 tests across 3 binaries

PASS [  0.002s] my_crate::tests::test_add
PASS [  0.003s] my_crate::tests::test_subtract
FAIL [  0.041s] my_crate::tests::test_database_connection

--- STDOUT:          my_crate::tests::test_database_connection ---
thread 'main' panicked at 'connection refused'
---

  Summary [  0.045s] 42 tests run: 41 passed, 1 failed

Failures are shown with full output at the end, not interleaved with other tests.

Configuration: .config/nextest.toml

nextest reads configuration from .config/nextest.toml at the workspace root:

[profile.default]
# How long before a test is considered hung
test-threads = "num-cpus"    # default: number of logical CPUs
slow-timeout = { period = "60s", terminate-after = 3 }
fail-fast = false             # keep running after first failure

[profile.default.junit]
path = "target/nextest/junit.xml"

[profile.ci]
# Stricter settings for CI
retries = 2
fail-fast = true

Use a profile with:

cargo nextest run --profile ci

Retry Logic for Flaky Tests

Configure automatic retries for flaky tests:

[profile.ci]
retries = { backoff = "exponential", count = 3, delay = "1s", max-delay = "10s" }

Or retry specific tests by name:

[[profile.ci.overrides]]
filter = 'test(network_)'
retries = 3

Only tests matching the filter get retries. This lets you apply retry logic to known-flaky integration tests without masking real failures in unit tests.

Per-Test Timeouts

Set timeouts globally or per-test:

[profile.default]
test-threads = 8

[[profile.default.overrides]]
filter = 'test(slow_)'
slow-timeout = { period = "120s" }  # warn after 2 minutes

[[profile.default.overrides]]
filter = 'test(unit_)'
slow-timeout = { period = "5s", terminate-after = 2 }  # kill after 10s

Filtering Tests

nextest has a rich filter expression language:

# Run tests in a specific package
cargo nextest run -p my_package

<span class="hljs-comment"># Run tests matching a name pattern
cargo nextest run <span class="hljs-built_in">test(parser)

<span class="hljs-comment"># Run tests NOT matching a pattern
cargo nextest run <span class="hljs-string">'not test(slow)'

<span class="hljs-comment"># Run tests in a specific file
cargo nextest run <span class="hljs-string">'test(/integration/)'

<span class="hljs-comment"># Combine filters
cargo nextest run <span class="hljs-string">'test(parse) and not test(slow)'

JUnit Output for CI

Generate JUnit XML for test result dashboards:

[profile.ci.junit]
path = "target/nextest/junit.xml"
cargo nextest run --profile ci

Upload to GitHub Actions:

- name: Upload test results
  uses: actions/upload-artifact@v4
  if: always()
  with:
    name: test-results
    path: target/nextest/junit.xml

GitHub Actions Integration

A complete CI workflow with nextest:

name: Tests
on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
      - uses: taiki-e/install-action@nextest

      # Build and test (nextest doesn't run doc tests)
      - name: Run tests
        run: cargo nextest run --profile ci --workspace

      # Doc tests still need cargo test
      - name: Run doc tests
        run: cargo test --doc --workspace

Limitations

nextest doesn't support:

  • Doc testscargo test --doc still required for doc tests
  • --nocapture with live output — nextest captures stdout/stderr per-test by design; --no-capture disables this but changes the output format

For doc tests in CI, run both:

cargo nextest run --workspace
cargo test --doc --workspace

Archive and Replay

nextest can archive test binaries and replay them on a different machine:

# Build and archive
cargo nextest archive --workspace --archive-file tests.tar.zst

<span class="hljs-comment"># Run the archive (no Rust toolchain needed on this machine)
cargo nextest run --archive-file tests.tar.zst

This is useful for separating build and test jobs in CI, or running tests on bare-metal machines without a full Rust install.

Comparing cargo test vs cargo nextest

Feature cargo test cargo nextest
Execution model Threads in one process Process per test
Output Interleaved Per-test, summary at end
Retries None Built-in
Per-test timeouts None Configurable
Doc tests Yes No (use cargo test --doc)
JUnit output Needs third-party crate Built-in
CI speedup Baseline 1.5-3× faster typical

Migrating from cargo test

The migration is usually:

  1. Install nextest
  2. Replace cargo test with cargo nextest run in scripts and CI
  3. Add cargo test --doc to cover doc tests
  4. Add .config/nextest.toml with retry and timeout configuration
  5. Enable JUnit output if your CI dashboard supports it

Most test suites work without modification. If you have tests that rely on shared global state across tests (which is technically undefined behavior under cargo test too), they may fail under nextest's process isolation. Fix the isolation rather than work around it.

Monitoring Beyond Tests

nextest handles test execution in development and CI. For 24/7 production monitoring — ensuring your deployed service behaves correctly under real conditions — HelpMeTest runs your test scenarios continuously against live endpoints. No source code, no Rust toolchain needed.

Summary

  • nextest is a drop-in replacement for cargo test with process-per-test isolation
  • Typical CI speedup: 1.5-3× on large test suites
  • Built-in retry logic for flaky tests with backoff configuration
  • Per-test timeouts prevent hung tests from blocking CI
  • JUnit XML output for test result dashboards
  • Doc tests still require cargo test --doc
  • Archive/replay for split build+test CI pipelines

Read more