Rust - Tests


Rust comes with a unit and integration test suit baked into cargo. To make a function a test function, add #[test] on the line before the function.

Tests in rust have access to the private parts of everything.

The following is an example of a unit test:

#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    fn new(w: u32, h: u32) -> Rectangle {
        Rectangle {width: w, height: h}
    }

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

#[cfg(test)]
mod tests {
    use super::*;  // Brings outer module into scope
    
    #[test]
    fn larger_can_hold_smaller() {
        let larger = Rectangle::new(5, 6);
        let smaller = Rectangle::new(4, 5);

        assert!(larger.can_hold(&smaller));
    }

    #[test]
    fn smaller_cannot_hold_larget() {
        let smaller = Rectangle::new(4, 5);
        let larger = Rectangle::new(5, 6);

        assert!(!smaller.can_hold(&larger));
    }

    #[test]
    fn it_works() -> Result<(), String> {
        if 2 + 2 == 4 {
            Ok(())
        } else {
            Err(String::from("2 + 2 != 4"))
        }
    }
}

Asserts

  • assert!(x): Passes if x is true
  • assert_eq!(x, y): Passes if x and y are equal
  • assert_ne!(x, y): Passes if x and y are not equal

Custom Failure Messages

Custom failure messages are supported by the assert family micros.

pub fn concat(fname: &str, lname: &str) -> String {
    format!("{} {}", fname, lname)
}

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

    #[test]
    fn combine_names() {
        let full_name = concat("Daltie", "Colti");
        assert_eq!(
            full_name,
            "Daltie Coltie",
            "Daltie Coltie != {}",
            full_name
        ) // The same failure message logic can be applied to any assert! micro
    }
}

Integration Tests

Integration tests are external to your library. They can only call your library's public API.

Integration tests are only for library crates. For binary crates, only unit tests can be used.

Integration tests go inside of a tests directory next to your src directory. Example:

// src/lib.rs in crate addr
pub fn add_two(x: i32) -> i32 {
    x + 2
}

// tests/integration_tests.rs
use addr;

#[test]
fn it_adds_two() {
    assert_eq!(4, addr::add_two(2));
}
Submodules

Each file in the tests directory is compiled as its own separate crate. To prevent this, put code you don't want separately tested into subdirectories inside of tests. These subdirectories will not be tested, but can act as support code for your tests.

Should Panic

If a test should panic, add #[should_panic] before the function declaration.

#[test]
#[should_panic]
fn this_should_panic() {
    panic_function(1000);
}

Unit Tests

Unit tests are used to test how your code interacts with itself, testing one module in isolation at a time.

Location

Unit tests live along side your code in the src directory in the same file as the code that it is testing. To create a unit test, create a module called tests with the #[cfg(test)] annotation. "cfg" stands for configuration.

Privacy

Unit tests can test the private parts of your code.