Syntax Cache
BlogMethodFeaturesHow It WorksBuild a Game
  1. Home
  2. Rust
  3. Rust Testing Practice: #[test], assert!, Unit & Integration Tests
Rust25 exercises

Rust Testing Practice: #[test], assert!, Unit & Integration Tests

Practice Rust testing including #[test] functions, assert macros, test organization, and running tests with cargo test.

Cheat SheetCommon ErrorsQuick ReferencePractice
Warm-up1 / 2

Can you write this from memory?

Write the attribute that marks a function as a test.

On this page
  1. 1cargo test Command Cheat Sheet
  2. 2Compiler Error E0425: Cannot Find Function in Test Module
  3. 3Compiler Error E0603: Integration Test Imports Private Item
  4. 4Invalid Test Return Type
  5. 5Test Organization: Unit, Integration, and Doc Tests
  6. Unit Tests
  7. Integration Tests
  8. Sharing Code Between Integration Tests
  9. The Binary Crate Gotcha
  10. Doc Tests
  11. 6Useful Test Patterns
  12. 7Further Reading
cargo test Command Cheat SheetCompiler Error E0425: Cannot Find Function in Test ModuleCompiler Error E0603: Integration Test Imports Private ItemInvalid Test Return TypeTest Organization: Unit, Integration, and Doc TestsUseful Test PatternsFurther Reading

Rust has first-class testing support built into the language and tooling. #[test] marks test functions. assert! macros check conditions. cargo test runs everything.

Unit tests live alongside code. Integration tests live in a tests/ directory. Doc tests run code examples in documentation.

This page focuses on test syntax, organization, and the cargo test flags you'll use constantly.

Related Rust Topics
Rust Error Handling: Result, Option, ? OperatorRust Modules & Crates: mod, use, pub, Crate Structure

cargo test runs all tests. Use -- --no-capture to see println output. Use -- --ignored for slow tests. The -- separates cargo flags from test runner flags.

CommandDoes
cargo testRun all tests (unit + integration + doc)
cargo test test_nameRun tests matching "test_name"
cargo test -- --no-captureShow println! output
cargo test -- --ignoredRun only #[ignore] tests
cargo test -- --include-ignoredRun all tests including ignored
cargo test --test integration_testRun a specific integration test file
cargo test --docRun doc tests only
cargo test --libRun unit tests only

The -- separates cargo flags from test runner flags. Everything after -- goes to the test binary.

Ready to practice?

Start practicing Rust Testing: #[test], assert!, Unit & Integration Tests with spaced repetition

The test module is a child scope—add use super::*; as the first line inside mod tests to import everything from the parent module.

The test module is a child scope — it cannot see the parent's items without importing them:

fn add(a: i32, b: i32) -> i32 { a + b }

// WRONG: test can't find add()
// #[cfg(test)]
// mod tests {
//     #[test]
//     fn test_add() {
//         assert_eq!(add(2, 2), 4);  // error[E0425]: cannot find function
//     }
// }

// RIGHT: import from parent module
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_add() {
        assert_eq!(add(2, 2), 4);
    }
}

use super::*; imports everything from the parent module. Add it as the first line inside mod tests.

Integration tests in tests/ are external crates — they can only see your public API:

// src/lib.rs
fn internal_helper() -> i32 { 42 }  // private
pub fn public_api() -> i32 { internal_helper() }

// tests/integration_test.rs
// WRONG: private items are not visible
// use my_crate::internal_helper;  // error[E0603]: function is private

// RIGHT: test the public API
use my_crate::public_api;

#[test]
fn test_api() {
    assert_eq!(public_api(), 42);
}

If you need to test internal logic, use a unit test inside the source file instead. Visibility rules determine what integration tests can access -- see modules and crates for details on pub, pub(crate), and module structure.

#[test] functions must return a type that implements Termination: either () or Result<(), E> where E: Debug:

// WRONG: i32 doesn't implement Termination
// #[test]
// fn test_parse() -> i32 {
//     "42".parse().unwrap()
// }

// RIGHT: return Result to use ? in tests
#[test]
fn test_parse() -> Result<(), Box<dyn std::error::Error>> {
    let n: i32 = "42".parse()?;
    assert_eq!(n, 42);
    Ok(())
}

Returning Result lets you use ? instead of .unwrap() chains, and the error message on failure is more informative. The error handling guide covers the ? operator and Result combinators in depth.

Unit tests go inside the source file (#[cfg(test)] mod tests) and can test private functions. Integration tests go in tests/ and only see your public API. Doc tests run code examples in documentation.

Unit Tests

Go inside the source file in a #[cfg(test)] mod tests block. Can test private functions:

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_works() {
        assert_eq!(2 + 2, 4);
    }
}

#[cfg(test)] means this code is only compiled when running tests — it stays out of production builds. The error handling cheat sheet is a useful companion when writing assertions and test helpers.

Integration Tests

Live in the tests/ directory at the crate root. Each file is compiled as a separate crate:

// tests/integration_test.rs
use my_crate::public_function;

#[test]
fn test_public_api() {
    assert!(public_function());
}

Sharing Code Between Integration Tests

Each file in tests/ is a separate crate, so sharing helpers requires a module:

// tests/common/mod.rs  (must use mod.rs style, not common.rs)
pub fn setup() -> TestContext {
    // shared setup code
}

// tests/integration_test.rs
mod common;

#[test]
fn test_with_setup() {
    let ctx = common::setup();
    // ...
}

The Binary Crate Gotcha

Integration tests import your crate with use my_crate::..., which only works for library crates (src/lib.rs). If your project is binary-only (src/main.rs), move testable logic into src/lib.rs and have main.rs call into it.

Doc Tests

Code examples in doc comments run with cargo test:

/// Adds two numbers.
///
/// ```
/// let result = my_crate::add(2, 3);
/// assert_eq!(result, 5);
/// ```
pub fn add(a: i32, b: i32) -> i32 { a + b }

Doctest attributes control compilation and execution:

AttributeCompiles?Runs?Use for
(none)YesYesNormal examples
no_runYesNoExamples that need network/files
ignoreNoNoIncomplete or broken examples
should_panicYesYes (expects panic)Demonstrating panics
compile_failYes (expects error)NoShowing what doesn't compile

Testing panics — expected does a substring match on the panic message:

#[test]
#[should_panic(expected = "divide by zero")]
fn test_panic() {
    divide(1, 0);
}

Ignored tests with reason:

#[test]
#[ignore = "requires database connection"]
fn expensive_test() {
    // run with: cargo test -- --ignored
}

Custom assertion messages — add context to failures:

#[test]
fn test_bounds() {
    let val = compute();
    assert!(val > 0, "expected positive value, got {val}");
    assert_eq!(val, 42, "compute() should return 42, got {val}");
}

  • The Rust Book: Testing — unit, integration, and doc tests
  • The Rust Book: Test Organization — cfg(test), tests/ directory
  • Rust By Example: Testing — practical test patterns
  • Rustdoc: Documentation Tests — doctest attributes and syntax

When to Use Rust Testing: #[test], assert!, Unit & Integration Tests

  • Write unit tests for individual functions and modules.
  • Write integration tests for public API behavior.
  • Use #[should_panic] for testing error conditions.
  • Use #[ignore] for slow tests run separately.
  • Use doc tests to keep examples in sync with code.

Check Your Understanding: Rust Testing: #[test], assert!, Unit & Integration Tests

Prompt

How do you organize tests in a Rust project?

What a strong answer looks like

Unit tests go in a `#[cfg(test)] mod tests` inside each source file—they can test private functions. Integration tests go in the `tests/` directory and only test public APIs. Doc tests are code examples in documentation comments.

What You'll Practice: Rust Testing: #[test], assert!, Unit & Integration Tests

Write test functions with #[test]Use assert!, assert_eq!, assert_ne! with custom messagesTest panics with #[should_panic(expected)]Skip tests with #[ignore] and run with --ignoredOrganize unit tests in #[cfg(test)] modulesWrite integration tests in tests/ directoryShare helpers between integration tests (common/mod.rs)Return Result from tests for ? usageUse cargo test flags (--no-capture, --doc, --test)

Common Rust Testing: #[test], assert!, Unit & Integration Tests Pitfalls

  • Test says "cannot find function" -- your test module is missing `use super::*;`. Without it, nothing from the parent module is in scope. Add `use super::*;` as the first line inside `mod tests`.
  • All your assertions pass but the test still fails -- you returned something other than `()` or `Result<(), E>` where `E: Debug`. Change the return type or return nothing.
  • Test failure says "left: 5, right: 5" with no context -- you used `assert!(a == b)` instead of `assert_eq!(a, b)`. `assert_eq!` prints both values and accepts a custom message.
  • Integration test gets "function is private" (E0603) -- tests in `tests/` only see your public API. Either make the item public or write a unit test inside the source file.
  • Tests pass but println! output is invisible -- `cargo test` captures stdout by default. Use `cargo test -- --no-capture` to see output.
  • `#[ignore]` tests never run in CI -- ignored tests need `cargo test -- --ignored` or `--include-ignored`. Add this to your CI config explicitly.

Rust Testing: #[test], assert!, Unit & Integration Tests FAQ

What is the difference between assert! and assert_eq!?

`assert!(condition)` checks a boolean. `assert_eq!(a, b)` checks equality and prints both values on failure, making debugging easier. Use `assert_eq!` when comparing values.

How do I test private functions?

Unit tests in the same file (inside `#[cfg(test)] mod tests`) can access private functions via `use super::*`. Integration tests cannot—they only see public APIs.

How do I run a specific test?

`cargo test test_name` runs tests containing "test_name" in their path. `cargo test --test integration_test` runs a specific integration test file.

What is #[cfg(test)]?

Conditional compilation. Code inside `#[cfg(test)]` is only compiled when running tests. This keeps test code out of production builds.

Can tests return Result?

Yes. Tests can return `Result<(), E>` where E: Debug. The test passes if Ok, fails if Err. This lets you use `?` for cleaner error handling instead of `.unwrap()` chains.

Why doesn't println! show in test output?

`cargo test` captures stdout by default. Use `cargo test -- --no-capture` to see printed output. Only failing tests show their output by default.

What are doctest attributes like no_run and compile_fail?

`no_run` compiles but skips execution (for examples needing I/O). `compile_fail` expects a compile error (for showing invalid code). `ignore` skips entirely. `should_panic` expects a runtime panic.

Rust Testing: #[test], assert!, Unit & Integration Tests Syntax Quick Reference

Basic test
#[test]
fn it_works() {
    assert!(true);
}
assert_eq
assert_eq!(add(2, 2), 4);
assert_ne
assert_ne!(result, 0);
With message
assert!(x > 0, "x must be positive, got {}", x);
Should panic
#[test]
#[should_panic]
fn test_panic() {
    panic!("boom");
}
Expected panic
#[should_panic(expected = "error")]
Ignore test
#[test]
#[ignore]
fn slow_test() {
    assert_eq!(2 + 2, 4);
}
Test module
#[cfg(test)]
mod tests { use super::*; }
Result test
#[test]
fn test() -> Result<(), Box<dyn std::error::Error>> {
    let n: i32 = "42".parse()?;
    assert_eq!(n, 42);
    Ok(())
}
Ignore with reason
#[test]
#[ignore = "needs database"]
fn slow_test() {}

Rust Testing: #[test], assert!, Unit & Integration Tests Sample Exercises

Example 1Difficulty: 1/5

Add the attribute to mark this function as a test that will run with `cargo test`.

#[test]
Example 2Difficulty: 1/5

Use the assertion macro that checks two values are equal.

assert_eq!
Example 3Difficulty: 1/5

Use the assertion macro that checks a boolean condition is true.

assert!

+ 22 more exercises

Further Reading

  • Rust Newtype Pattern: Catch Unit Bugs at Compile Time18 min read

Start practicing Rust Testing: #[test], assert!, Unit & Integration Tests

Free daily exercises with spaced repetition. No credit card required.

← Back to Rust Syntax Practice
Syntax Cache

Build syntax muscle memory with spaced repetition.

Product

  • Pricing
  • Our Method
  • Daily Practice
  • Design Patterns
  • Interview Prep

Resources

  • Blog
  • Compare
  • Cheat Sheets
  • Vibe Coding
  • Muscle Memory

Languages

  • Python
  • JavaScript
  • TypeScript
  • Rust
  • SQL
  • GDScript

Legal

  • Terms
  • Privacy
  • Contact

© 2026 Syntax Cache

Cancel anytime in 2 clicks. Keep access until the end of your billing period.

No refunds for partial billing periods.