Every way to test Rust code
Unit tests, integration tests, doc tests, table tests, property tests, test doubles, fuzzing, Miri, and tests for async code and web services. What each kind catches.
Ask what you're trying to catch.
- A function gives the wrong answer for an input you can name → a
unit test in a
#[cfg(test)] mod testsbeside the code. Several inputs with the same shape → a table of cases in one test. - The public API doesn't work the way a user would call it → an
integration test in
tests/, which sees only what'spub. - The example in your documentation is wrong → a doc test. It
runs with
cargo testand keeps the docs honest. - Some input you didn't think of breaks it → a property test (state a rule that must hold for every input and let the tool search), or a fuzzer for a parser that takes untrusted bytes.
- The code talks to something slow or unpredictable (the clock, the network, a payment service) → put that dependency behind a trait and pass a test double.
- Unsafe code might have undefined behaviour → run the tests under Miri.
- The code is async, or a whole HTTP service →
#[tokio::test], paused time, and requests sent to the router without a real port.
The three built-in kinds
Section
Unit tests and assertions
#[test], assert_eq!, and #[should_panic].
Section
Integration tests
Each file in tests/ is its own crate that uses yours from outside.
Formal. Each .rs file directly inside a package's tests/ folder
is compiled by cargo test as a separate crate that depends on the
package's library crate. It can therefore use only items reachable
through the library's public API, exactly as another crate could.
Binary crates (src/main.rs) cannot be imported by integration tests
at all, which is one reason to put logic in src/lib.rs.
In plain English. A test in the tests/ folder is an outsider. It uses your library the same way a stranger would, through the front door, so it checks what users will really experience.
Example. If src/lib.rs has a private fn normalize, a unit test
inside src/lib.rs can call it, but tests/api.rs gets E0603.
Why this rule exists. Testing through the public API catches mistakes in what users actually see, and it lets the internals change without rewriting the tests.
Why an integration test can't reach a private helper, and why that's the point.
Section
Documentation tests
Code in a doc comment is compiled and run.
More inputs than you'd write by hand
A table of cases, then a rule checked against hundreds of generated inputs.
Section
Fuzzing and test runners
cargo fuzz for untrusted input, and cargo nextest for large suites.
Example
format_cents printed gives back the same number" checked for thousands of random amounts, not just 0, 1 and 999.Isolating the code under test
Section
Fixtures and organization
Shared setup, temporary directories, and tests that don't depend on each other's order.
Section
Mocking with traits
The smallest trait for the dependency, and a hand-written double.
Logic in lib.rs so tests can call it.
Special cases
Section
Testing async code
#[tokio::test], timeouts so a hang fails, and paused time for retries.
Section
Testing a service
Send requests straight to the router, with no network and no fixed port.
Section
Checking unsafe code
Miri runs your tests and reports undefined behaviour that normal runs miss.
When a test fails
Section
Debugging a failing test
Run just that one, show its output, and narrow it down.
Section
Tests in CI
Every test, on every target OS, on every change.