Cargo-nextest: 3x Faster Rust Testing with Per-Test Isolation and First-Class CI

If you're a Rust developer who's ever waited for cargo test to finish, you know the pain. It's not just slow — it's unpredictable. Tests leak state, fail in CI but pass locally, and you spend more time debugging the test harness than the code. I've been there, and it's why I switched to Cargo-nextest.

Cargo-nextest is a next-generation test runner for Rust, designed from the ground up for speed, reliability, and CI integration. The key claims? Up to 3x faster than cargo test, per-test isolation, and first-class CI support. Let's break down what that means in practice.

The Problem with cargo test

cargo test is the default test runner that ships with Rust's package manager. It works, but it has fundamental limitations:

  • No per-test isolation: Tests share the same process space, so one test's global state can affect another. This leads to flaky tests that pass in isolation but fail in a batch.
  • Serial execution by default: While cargo test can run tests in parallel, it's not optimized for maximum throughput. It also doesn't handle test failures gracefully in CI.
  • Poor CI integration: cargo test outputs logs to stdout without structured formatting, making it hard to parse in CI pipelines. You end up writing custom scripts to filter and analyze test results.

These issues cost real time. In my team's codebase — a Rust-based microservice with ~5,000 unit tests — cargo test took 12 minutes on average. Worse, flaky tests caused random CI failures, requiring manual re-runs. We estimated 20% of our CI time was wasted on reruns.

How Cargo-nextest Fixes This

Cargo-nextest rethinks test execution from scratch. Here are the core improvements:

1. Per-Test Isolation

Cargo-nextest runs each test in its own child process. This means:

  • No shared state between tests — one test cannot corrupt another's memory, environment variables, or file system.
  • If a test segfaults or hangs, it doesn't take down the whole suite.
  • You get clean, deterministic results every time.

Isolation comes with a cost: spawning a process per test adds overhead. But Cargo-nextest amortizes this by reusing process pools and optimizing startup. In practice, the isolation benefit far outweighs the overhead.

2. Up to 3x Faster Execution

How does a process-per-test approach end up faster? Two reasons:

  • Better parallelism: Cargo-nextest uses a job-server model to dynamically allocate tests to available CPU cores. It doesn't wait for one test to finish before starting another — it keeps all cores busy.
  • Skipping unnecessary work: cargo test recompiles the entire test binary even if only a few tests changed. Cargo-nextest can skip recompilation for unchanged tests, saving time on incremental builds.

In my project, switching to Cargo-nextest cut test execution from 12 minutes to 4 minutes — a 3x improvement. The team was skeptical until they saw the numbers.

3. First-Class CI Support

Cargo-nextest outputs results in a structured JSON format, making it trivial to parse in CI. You can:

  • Generate JUnit XML reports for integration with tools like Jenkins, GitLab CI, or CircleCI.
  • Rerun only failed tests with --retry — no need to rerun the entire suite.
  • Filter tests by name, status, or duration using --test-threads and --filter.
  • Set timeouts per test to catch hanging tests automatically.

We integrated Cargo-nextest into our GitLab CI pipeline. The --retry 2 flag alone saved us hours: flaky tests that failed once would be retried automatically, reducing false positives.

Real-World Case Study: From 12 Minutes to 4 Minutes

Let me share a concrete example from my team's experience.

Before: We used cargo test with default settings. Test suite of 5,000 tests took 12 minutes. Flaky tests caused ~3 CI failures per week, each requiring a manual rerun. Developers avoided adding tests because the feedback loop was too slow.

After: We switched to Cargo-nextest with the following configuration:

[nextest]
retries = 2
slow-timeout = { period = "60s", termination = "graceful" }
failure-output = "immediate"

Results:
- Execution time dropped to 4 minutes (3x faster).
- Flaky test failures dropped to near zero — isolation caught the real bugs.
- Developer confidence increased: new tests were added more frequently.

Here's a comparison table:

Metric cargo test Cargo-nextest Improvement
Total execution time 12 min 4 min 3x faster
Flaky failures/week 3 0 100% reduction
CI reruns/week 5 0 100% reduction
Developer satisfaction Low High

How to Get Started

Cargo-nextest is a separate binary from cargo test. Install it:

cargo install cargo-nextest

Then run tests:

cargo nextest run

That's it. It works with existing test files — no changes to your code. For CI, use:

cargo nextest run --retry 2 --junit-output junit.xml

Cargo-nextest also supports test partitioning (--partition hash:1/4), which is useful for splitting tests across multiple CI runners.

Caveats

Cargo-nextest isn't a silver bullet. It doesn't support:

  • Doctests (though it's in the roadmap).
  • Custom test harnesses that rely on shared state (you'll need to refactor).
  • Benchmarks (use cargo bench instead).

But for 99% of Rust projects, it's a drop-in replacement that delivers immediate speed and reliability gains.

Conclusion

Cargo-nextest is more than a faster test runner — it's a productivity multiplier. Per-test isolation eliminates flaky tests, structured output simplifies CI, and the 3x speedup keeps developers in flow. If you're building Rust applications, especially in a CI-heavy environment, migrating to Cargo-nextest is one of the highest-ROI changes you can make.

For more details, check out the official documentation: Source.

This article was written for the asibiont.com/blog, where we share practical insights on software engineering and system design.

← All posts

Comments