Skip to content

Testing

Updated
17 Sept 2026
4 min read
0 sections
Page 5 of 18
Rust
  • create a new library project named adder.
  • lib.rs is the crate root of the library crate.
rust
pub fn add(left: usize, right: usize) -> usize {
    left + right
}

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

    #[test]
    fn it_works() {
        let result = add(2, 2);
        assert_eq!(result, 4);
    }
}
  1. assert!{:rust} macro is used to check if the result of the function is true.
  2. assert_eq!{:rust} macro is used to compare the result of the function with the expected value.
  3. assert_ne!{:rust} macro is used to check if the two values are not equal.
  • #[cfg(test)]{:rust} attribute is used to compile the test code only when running tests.
  • #[test]{:rust} attribute is used to define a test function.
Out · error
$ cargo test

running 1 test
test tests::it_works ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
  • A test fails when something inside the test function panics.
  • Each test function runs in a new thread, if the main thread sees that the new thread has panicked, it reports the test as failed.
rust
pub fn add(left: usize, right: usize) -> usize {
    left + right
}

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

    #[test]
    fn it_works() {
        let result = add(2, 2);
        assert_eq!(result, 4);
    }

    #[test]
    fn failing_test() {
        panic!("Test failed");
    }
}
Out · error
$ cargo test

running 2 tests
test tests::it_works ... ok
test tests::failing_test ... FAILED
6 more lines

failures:

---- tests::failing_test stdout ----
thread 'tests::failing_test' panicked at srclib.rs:17:9:
Test failed
rust
struct Rectangle {
    width: i32,
    height: i32,
}

impl Rectangle {
    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width > other.width && self.height > other.height
    }
}

#[cfg(test)]
mod tests {

    use super::*;

    #[test]
    fn it_works() {
        let rect = Rectangle {
            width: 5,
            height: 4,
        };

        let small_rect = Rectangle {
            width: 4,
            height: 3,
        };

        assert!(rect.can_hold(&small_rect));
    }
}
Out · error
$ cargo test

running 1 test
test tests::it_works ... ok
6 more lines
successes:

successes:
    tests::it_works

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
  • use super::*{:rust} brings the Rectangle struct into tests{:rust} module scope.

Custom Failure Messages

rust
pub fn add(left: usize, right: usize) -> usize {
    left + right
}

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

    #[test]
    fn add_working() {
        let result = add(2, 2);
        assert_eq!(result, 5, "2 + 2 should be 5, not {}", result);
    }
}
Out · error
$ cargo test

running 1 test
test tests::add_working ... FAILED
7 more lines
successes:

failures:

---- tests::add_working stdout ----
thread 'tests::add_working' panicked at srclib.rs:63:9:
assertion `left == right` failed: 2 + 2 should be 5, not 4

Asserting that a Function Panics

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

    #[test]
    #[should_panic]
    fn add_working() {
        let result = add(2, 2);
        assert_eq!(result, 5, "2 + 2 should be 5, not {}", result);
    }
}
Out · error
$ cargo test

running 2 tests
test tests::it_works ... ok
test tests::add_working - should panic ... ok

test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
  • #[should_panic]{:rust} attribute is used to check if the test function panics.
rust
pub struct Guess {
    value: i32,
}

impl Guess {
    pub fn new(value: i32) -> Guess {
        if value < 1 {
            panic!("Guess Value must be greater than 0, got {}", value);
        } else if value > 100 {
            panic!("Guess Value must be less than 101, got {}", value);
        }

        Guess { value }
    }
}

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

    #[test]
    #[should_panic(expected = "Guess Value must be less than 101")]
    fn valid_guess() {
        Guess::new(101);
    }

    #[test]
    #[should_panic(expected = "Guess Value must be less than 101")]
    fn valid_guess2() {
        Guess::new(0);
    }
}
Out · error
$ cargo test

running 2 tests
test tests::valid_guess - should panic ... ok
test tests::valid_guess2 - should panic ... FAILED
9 more lines

failures:

---- tests::valid_guess2 stdout ----
thread 'tests::valid_guess2' panicked at srclib.rs:73:13:
Guess Value must be greater than 0, got 0
note: panic did not contain expected string
      panic message: `"Guess Value must be greater than 0, got 0"`,
 expected substring: `"Guess Value must be less than 101"`
  • #[should_panic(expected = "message")]{:rust} attribute is used to check if the test function panics with the expected message.

Returning Result Types

rust
#[cfg(test)]
mod tests {
    #[test]
    fn it_works() -> Result<(), String> {
        if 2 + 3 == 4 {
            Ok(())
        } else {
            Err(String::from("2 plus 2 does not equal four"))
        }
    }
}
Out · error
$ cargo test

running 1 test
test tests::it_works ... FAILED
4 more lines
failures:

---- tests::it_works stdout ----
Error: "2 plus 2 does not equal four"

Showing Output

rust
fn prints_and_return_10(a: i32) -> i32 {
    println!("I got the value {}", a);

    10
}

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

    #[test]
    fn this_test_will_pass() {
        let value = prints_and_return_10(4);

        assert_eq!(10, value);
    }

    #[test]
    fn this_test_will_fail() {
        let value = prints_and_return_10(8);
        assert_eq!(5, value)
    }
}
text
$ cargo test -- --show-output

running 2 tests
test tests::this_test_will_pass ... ok
test tests::this_test_will_fail ... FAILED

failures:

---- tests::this_test_will_fail stdout ----
I got the value 8
thread 'tests::this_test_will_fail' panicked at src\lib.rs:131:9:
assertion `left == right` failed
  left: 5
 right: 10
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Running specific tests

Out · error
$ cargo test this_test_will_pass

running 1 test
test tests::this_test_will_pass ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out; finished in 0.00s
  • cargo test test_name{:shell} is used to run a specific test, which starts with the name test_name{:shell}.

Ignoring Tests

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

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

    #[test]
    #[ignore]
    fn expensive_test() {
        // code that takes an hour to run
    }
}
Out · error
$ cargo test

running 1 test
test tests::it_works ... ok

test result: ok. 1 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.00s
  • #[ignore]{:rust} attribute is used to ignore the test function.

Running Ignored Tests

Out · error
$ cargo test -- --ignored

running 1 test
test tests::expensive_test ... ignored

test result: ok. 0 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.00s

Test Organization

  • Unit tests are small and more focused, test one module in isolation and test private interfaces.
  • Integration tests are larger and test the entire program, test the public interfaces that the library provides to other libraries.
use adder; fn it_adds_two(){ assert_eq!(adder::add(2, 2), 4); }
  • cargo test --test test_name{:shell} is used to run a specific integration test.
  • tests{:rust} directory treats each file as a separate crate, (this leads unexpected behavior when using use super::*{:rust}).
  • multiple integration test files and you want to share common code, you can put the common code into a tests/common.rs File
pub fn setup() { // setup code }
  • Here common.rs{:rust} is treated like a integration test file(not intended).

To avoid this issue

pub fn setup() { // setup code }
  • common{:rust} directory is treated as a module not as test crate, so it doesn't run as a test.
  • common/mod.rs allows to use the common code in the integration test file.
use adder; mod common; #[test] fn it_adds_two() { common::setup(); assert_eq!(4, adder::add_two(2)) }
  • this way the common code can be shared between multiple integration test files.
- lib.rs creates a library crate. - main.rs creates an binary crate.
Cannot test binary crates with the integration tests.