Skip to content
rusthacks.dev
Testing

Running Rust Tests Faster With Cargo Nextest in CI

cargo-nextest runs each test in its own process with better parallelism, clear output, retries and JUnit reports. See install, speed expectations, config and what it does not run.

3 min read
Summarize in AI
Flat illustration for the post “Running Rust Tests Faster With Cargo Nextest in CI”, composed around testing.

cargo test runs the tests of one binary in threads, and the binaries one after another. For a workspace with many crates, that leaves cores idle. cargo-nextest is a different runner that schedules all tests across the machine and gives each its own process. The output is also easier to read. This post covers installing, the config you actually need and what it does not do. It supports the CI advice in the testing in Rust guide.

cargo-nextest 0.9, Rust 1.90.

Install and run

Shell
cargo install cargo-nextest --locked
cargo nextest run

Output has this shape, with your own names and numbers:

Text
    Starting 142 tests across 8 binaries
        PASS [   0.012s] core parser::tests::parses_header
        PASS [   0.031s] api routes::list_users
     Summary [   2.115s] 142 tests run: 142 passed, 0 skipped

Each line shows the duration of one test, and slow tests are flagged. On a suite with many small binaries, the wall clock time often drops, because the cores stay busy; the amount depends on your suite. Measure both runners on your project with time.

Configuration

.config/nextest.tomlTOML
[profile.default]
fail-fast = false
slow-timeout = { period = "30s", terminate-after = 4 }

[profile.ci]
retries = 2
fail-fast = false
test-threads = 4

[profile.ci.junit]
path = "junit.xml"

Use cargo nextest run --profile ci in CI. slow-timeout marks tests slower than 30 seconds and terminates a hung test after four periods. retries = 2 reruns a failing test up to twice and marks it FLAKY if it later passes. junit.xml feeds CI dashboards.

Isolation, for better and worse

Because each test is its own process:

  • A global static initialized by one test does not leak to the next. Order dependencies surface as failures.
  • A test calling std::process::exit or aborting fails alone.
  • Per-process setup runs for every test, so expensive setup (starting a container) is repeated; use a wrapper script or a setup script defined in the config to share it.

That is the main behavior change if a suite uses once_cell or lazy_static shared state to avoid repeated setup.

Filtering

Shell
cargo nextest run -E 'package(core) and test(parser)'
cargo nextest run --run-ignored ignored-only

The filter expression language selects by package, binary, test name and more. Compare with name filters in ignoring and filtering tests.

Splitting across machines

Shell
cargo nextest run --partition count:1/3

Run 1/3, 2/3 and 3/3 on three CI jobs to split the work. Archive the build once with cargo nextest archive and run it elsewhere without recompiling.

Doc tests

Shell
cargo nextest run && cargo test --doc

Nextest does not run doc tests, because rustdoc compiles and runs them in a way the runner cannot intercept. Keep the second command in CI (doc tests).

Limits

For a small crate with a few dozen fast tests, cargo test is just as quick and needs nothing installed. Retry settings can mask real bugs; read flaky tests before turning them on. The tool's documentation is at nexte.st.

Also inTooling
Taggednextest
Share

Keep reading

Get new posts by email

We send one email when a new post is published. No spam.

Unsubscribe in one click. No spam, ever.