DEV Community

Subesh Yadav
Subesh Yadav

Posted on

🧪 Day 20 of #100DaysOfRust: Writing Tests in Rust 🦀

Hello, Rustaceans!
Today marks Day 20 of my #100DaysOfRust journey, and I dove into a key feature of Rust that ensures correctness and reliability — writing tests.

Rust has built-in support for testing with cargo test, and it provides excellent macros, attributes, and tools for writing clear, expressive, and robust test cases.


🧪 Anatomy of a Test Function

Every test function in Rust is marked with the #[test] attribute.

A basic test function:

#[test]
fn it_works() {
    // 1. Set up any needed data or state.
    let a = 2;

    // 2. Run the code you want to test.
    let b = a + 2;

    // 3. Assert that the result is what you expect.
    assert_eq!(b, 4);
}
Enter fullscreen mode Exit fullscreen mode

✅ Running Tests

Use this command to run all tests:

cargo test
Enter fullscreen mode Exit fullscreen mode

Rust compiles the tests in a separate test binary and shows clear output about passing or failing tests.


✔️ Checking Conditions with assert!

The assert! macro ensures that a boolean condition is true. If it's not, the test fails.

#[test]
fn assert_macro_example() {
    let value = true;
    assert!(value); // Passes
}
Enter fullscreen mode Exit fullscreen mode

Fails if:

assert!(false); // panics with default message
Enter fullscreen mode Exit fullscreen mode

You can also add a custom failure message:

assert!(1 + 1 == 3, "Math is broken!");
Enter fullscreen mode Exit fullscreen mode

🧮 Testing Equality with assert_eq! and assert_ne!

Rust provides macros to compare values:

  • assert_eq!(a, b) — passes if a == b
  • assert_ne!(a, b) — passes if a != b
#[test]
fn test_equality_macros() {
    assert_eq!(2 + 2, 4);
    assert_ne!(5, 3);
}
Enter fullscreen mode Exit fullscreen mode

When these fail, Rust shows both the expected and actual values, making debugging easier.


💬 Custom Failure Messages

You can also include custom messages in these macros:

#[test]
fn custom_message_example() {
    let result = 2 + 2;
    assert_eq!(result, 5, "Expected 5 but got {}", result);
}
Enter fullscreen mode Exit fullscreen mode

Output:

thread 'main' panicked at 'Expected 5 but got 4', src/main.rs:4:5
Enter fullscreen mode Exit fullscreen mode

💥 Testing for Panics with #[should_panic]

Some tests are expected to panic (i.e., crash intentionally under specific conditions).

Use the #[should_panic] attribute:

#[test]
#[should_panic]
fn panics_when_called() {
    panic!("This function panics!");
}
Enter fullscreen mode Exit fullscreen mode

Make it more precise using expected:

#[test]
#[should_panic(expected = "Guess value must be less than or equal to 100")]
fn panic_with_message() {
    panic!("Guess value must be less than or equal to 100");
}
Enter fullscreen mode Exit fullscreen mode

🎯 Writing Tests That Return Result<T, E>

Rust lets you write tests that return Result<(), E> for clearer error reporting.

#[test]
fn test_with_result() -> Result<(), String> {
    let value = 2 + 2;

    if value == 4 {
        Ok(())
    } else {
        Err("2 + 2 did not equal 4".into())
    }
}
Enter fullscreen mode Exit fullscreen mode

This lets you use ? inside your tests, which is especially useful when testing functions that return Result.


🧪 Summary

Concept Description
#[test] Marks a function as a unit test
assert!, assert_eq!, assert_ne! Macros to validate test conditions
#[should_panic] Use when a function should panic
Result<(), E> return type Clean test error reporting and ? usage
Custom messages Improve failure debugging

🗕️ What’s Next?

In Day 21, I’ll explore how to organize tests, including modules, integration tests, test hierarchies, and best practices.

Let me know your favorite testing tip in Rust!
Until tomorrow, happy coding! 🦀

Top comments (0)