cargo-nextest: Faster Rust Testing with Better Output
cargo test is built into Rust's toolchain and works well for simple cases. But as your project grows — more tests, slower builds, flaky network tests, CI that needs structured output — cargo test's limitations become friction. cargo-nextest is a replacement that runs tests faster, presents output more clearly, and integrates better with CI systems.
Why nextest
The headline number: nextest typically runs Rust test suites 2–3× faster than cargo test. This happens because nextest runs each test as its own process, enabling true parallelism. cargo test uses threads within a single process, which limits parallelism when tests share global state or block on I/O.
Other improvements:
- Structured output — test results as a clean table, not a wall of text
- Progress bar — see how many tests passed/failed/running in real time
- Test retries — automatically retry flaky tests, configurable per-test
- Partitioning — split tests across multiple CI machines
- JUnit XML output — CI systems can parse and display results
- Slow test detection — flag tests that exceed a time threshold
Installation
# Via cargo-binstall (fastest — downloads precompiled binary)
cargo binstall cargo-nextest
# Or via cargo install
cargo install cargo-nextest --locked
# Or download binary directly
curl -LsSf https://get.nexte.st/latest/linux | tar zxf - -C ~/.cargo/bin
Verify:
cargo nextest --version
Basic Usage
# Run all tests (replaces cargo test)
cargo nextest run
# Run tests matching a filter
cargo nextest run auth
# Run tests in a specific package
cargo nextest run -p my-crate
# List tests without running
cargo nextest list
# Run with increased parallelism
cargo nextest run --test-threads 16
The output looks different from cargo test:
Finished test [unoptimized + debuginfo] target(s) in 2.34s
Starting 47 tests across 3 test binaries
────────────── TESTS SKIPPED: 0 ──────────────────────────
────────────── TESTS PASSED: 47 ─────────────────────────
Test run complete. 47 passed, 0 skipped, 0 failed in 1.823s
Each test result shows the test name, duration, and outcome. Failures include captured output.
Configuration
Configure nextest in .cargo/nextest.toml (or in Cargo.toml under [profile.nextest]):
[profile.default]
# Fail fast after N test failures
fail-fast = false
# Retry flaky tests up to 2 times
retries = 2
# Slow test threshold (print warning)
slow-timeout = { period = "30s", terminate-after = 3 }
# Maximum parallel threads
test-threads = "num-cpus"
[profile.ci]
# CI profile: JUnit output, no progress bar
retries = 2
failure-output = "final"
status-level = "all"
final-status-level = "all"
Run with a specific profile:
cargo nextest run --profile ci
Per-Test Configuration
Some tests need special handling — integration tests that hit a real database, tests that must run serially, tests known to be slow:
# .cargo/nextest.toml
[[profile.default.overrides]]
# Tests tagged as requiring serial execution
filter = 'test(#[serial])'
test-threads = 1
[[profile.default.overrides]]
# Database integration tests get more retries
filter = 'test(/integration/) and test(/database/)'
retries = 5
[[profile.default.overrides]]
# Known slow test — don't fail on slow threshold
filter = 'test(test_large_data_processing)'
slow-timeout = { period = "5m" }
The filter syntax supports test names, binary names, tags, and boolean operators.
CI Integration
GitHub Actions
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@cargo-nextest
- name: Run tests
run: cargo nextest run --profile ci
- name: Upload JUnit results
uses: mikepenz/action-junit-report@v4
if: always()
with:
report_paths: 'target/nextest/ci/junit.xml'
Test Partitioning for Parallel CI
Split your test suite across multiple machines to run in parallel:
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
partition: [1, 2, 3, 4]
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: taiki-e/install-action@cargo-nextest
- name: Run tests (partition ${{ matrix.partition }}/4)
run: |
cargo nextest run \
--partition count:${{ matrix.partition }}/4 \
--profile ci
Four machines run in parallel, each handling a quarter of the tests.
Filtering Syntax
nextest's filter syntax is powerful:
# Run tests with "auth" in the name
cargo nextest run auth
# Run tests in a specific binary
cargo nextest run --test integration_tests
# Exclude slow tests
cargo nextest run 'not test(slow)'
# Combine filters
cargo nextest run 'test(auth) and not test(network)'
# Run only doc tests (note: nextest doesn't run doc tests — use cargo test for those)
Known Limitations
nextest does not run doc tests (cargo test --doc). For projects that rely heavily on doctests for documentation examples, you'll need to run both:
cargo nextest run # Unit and integration tests
cargo test --doc # Doc tests only
This is tracked as a known limitation on the nextest GitHub. For most projects, running both in CI is straightforward.
nextest vs cargo test
| Feature | nextest | cargo test |
|---|---|---|
| Speed | 2–3× faster | Baseline |
| Output format | Structured table | Flat text |
| Test isolation | Per-process | Per-thread |
| Flaky test retries | ✓ | ✗ |
| JUnit XML | ✓ | ✗ |
| CI partitioning | ✓ | ✗ |
| Doc tests | ✗ | ✓ |
| Standard toolchain | No (install required) | Yes |
For most Rust projects, nextest is worth adopting — the speed improvement alone justifies it, and better CI integration is a bonus. The only reason to stick with cargo test exclusively is if you depend heavily on doctests or can't add a binary dependency to your build.
Subscribe to DevTools Guide for more developer tool deep dives.