Sling Academy
Home/Rust/Writing Basic Unit Tests in Rust with the #[test] Attribute

Writing Basic Unit Tests in Rust with the #[test] Attribute

Last updated: January 06, 2025

Unit testing is a crucial aspect of Rust development that ensures your code runs as expected. Rust provides a robust testing framework built into its standard library, allowing developers to write tests alongside their code. This is usually done using the #[test] attribute, which is both easy to use and powerful.

Setup for Unit Testing in Rust

Getting started with unit tests in Rust is straightforward since the testing framework is built into the language. All you need is an existing Cargo project, or create one with:

cargo new my_project

Jump into the src folder of your project and open the main.rs file. Rust encourages putting unit tests in the same file as the code they test. Place your tests in a module annotated with #[cfg(test)].

Writing a Basic Test

Imagine you have a simple function add_two which adds two to a number. You can write a test for this function using the #[test] attribute. Here's how:

fn add_two(a: i32) -> i32 {
    a + 2
}

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

    #[test]
    fn test_add_two() {
        assert_eq!(add_two(2), 4);
    }
}

The #[test] attribute tells the compiler that this function is a test. In the example above, assert_eq! is used to check that add_two(2) equals 4.

Running Tests

To run your tests, simply use:

cargo test

This command will build your project in a special testing mode, execute the tests, and report the results. You will see output indicating whether each test passed or failed.

Handling Panics and Assertions

Tests in Rust will fail if they panic. You can explicitly fail a test by using assert! macros. Rust includes several such macros:

  • assert!: Checks that a condition is true.
  • assert_eq! and assert_ne!: Compare two values for equality or inequality.
  • assert!(condition, "message"): Optionally includes a custom failure message.

For example, you can use assert! to test complex conditions:

#[test]
fn test_complex_condition() {
    let value = some_complex_calculation();
    assert!(value > 10, "Value should be greater than 10");
}

This approach enables more flexible and informative testing.

Documenting Tests

Documentation comments above test functions can describe their intent and behaviors. This helps other developers understand what exactly is being tested and why it's important.

/// Tests the add_two function with a basic input.
#[test]
fn test_add_two_basic_input() {
    assert_eq!(add_two(0), 2);
}

Testing Edge Cases

Good tests cover not just the 'happy path', but also edge cases. For instance, check how add_two handles a negative number:

#[test]
fn test_add_two_negative_input() {
    assert_eq!(add_two(-5), -3);
}

Advanced Testing Concepts

As you write more tests, you'll find Rust supports advanced features, including benchmarks and property-based testing. Diving into these topics is beyond this article's scope, but Rust's wide support for testing means you can customize every aspect of your test suite.

Conclusion

Writing unit tests in Rust with the #[test] attribute is essential to produce reliable, robust, and maintainable software. By integrating tests directly within your code files, you leverage Rustic idioms and develop a deeper understanding of your program’s intricacies. As you continue to master unit tests, you’ll find Rust’s testing pattern a powerful ally in your development toolkit.

Next Article: Running Tests in Rust Using the cargo test Command

Previous Article: Introduction to Testing in Rust: Setting Up a Test Environment

Series: Testing in Rust

Rust

You May Also Like

  • E0557 in Rust: Feature Has Been Removed or Is Unavailable in the Stable Channel
  • Network Protocol Handling Concurrency in Rust with async/await
  • Using the anyhow and thiserror Crates for Better Rust Error Tests
  • Rust - Investigating partial moves when pattern matching on vector or HashMap elements
  • Rust - Handling nested or hierarchical HashMaps for complex data relationships
  • Rust - Combining multiple HashMaps by merging keys and values
  • Composing Functionality in Rust Through Multiple Trait Bounds
  • E0437 in Rust: Unexpected `#` in macro invocation or attribute
  • Integrating I/O and Networking in Rust’s Async Concurrency
  • E0178 in Rust: Conflicting implementations of the same trait for a type
  • Utilizing a Reactor Pattern in Rust for Event-Driven Architectures
  • Parallelizing CPU-Intensive Work with Rust’s rayon Crate
  • Managing WebSocket Connections in Rust for Real-Time Apps
  • Downloading Files in Rust via HTTP for CLI Tools
  • Mocking Network Calls in Rust Tests with the surf or reqwest Crates
  • Rust - Designing advanced concurrency abstractions using generic channels or locks
  • Managing code expansion in debug builds with heavy usage of generics in Rust
  • Implementing parse-from-string logic for generic numeric types in Rust
  • Rust.- Refining trait bounds at implementation time for more specialized behavior