DEV Community

Thiago Massari Guedes
Thiago Massari Guedes

Posted on

A simple guide to unit tests in Rust

A very important part of any software are the unit tests, after all, they help us verifying that the cases that are in our heads are indeed correctly implemented and also that whoever is the next lucky fellow changing our code in the future (it might be ourselves!) will feel confident that their change won't break the application.

Rust implements tests in the same file that you are implementing your functions. I will first show one example and later explain how it works.

Creating the Unit Test

First, let's see a piece of code I used in a tool to generate posts for my newly rewritten blog system Texted2

fn render_header(id: &str, name: &str, date: &str, title: Option<&str>) -> String {
    let mut buf = String::new();
    let _ = writeln!(&mut buf, "[ID]: # ({})", id);
    let _ = writeln!(&mut buf, "[DATE]: # ({})", date);
    let _ = writeln!(&mut buf, "[AUTHOR]: # ({})", name);
    let _ = writeln!(&mut buf, "");
    if let Some(title) = title {
        let _ = writeln!(&mut buf, "# {}", title);
    } else {
        let _ = writeln!(&mut buf, "# Replace with title");
    }
    buf
}
Enter fullscreen mode Exit fullscreen mode

Let's say I want to generate a unit test to verify this header is correctly generated.

In the file test_data.rs, I will create the expected post header

pub(crate) const HEADER_DATA: &str = r#"[ID]: # (bcfc427f-f9f3-4442-bfc2-deca95db96d5)



# This is a title
"#;
Enter fullscreen mode Exit fullscreen mode

And now I am going to write my unit test

fn render_header(id: &str, name: &str, date: &str, title: Option<&str>) -> String {
    // ...
}

// First, note the test is in the same file
// Second, #[cfg(test)] tells the compiler that this code only is used then configuration = test
#[cfg(test)]
mod tests {
    // import POST_DATA const
    use test_data::POST_DATA;
    // import everything defined in the file (super from mod tests)
    // So, I can use render_header instead of super::render_header
    use super::*;

    // And now our unit test.
    #[test]
    fn test_happy_case() {
        let id = "bcfc427f-f9f3-4442-bfc2-deca95db96d5";
        let name = "Thiago";
        let date = "2024-02-27 06:20:53.000";
        let title = "This is a title";
        let header = render_header(&id, &name, &date, Some(title));

        assert_eq!(header, POST_DATA);
    }
}
Enter fullscreen mode Exit fullscreen mode

The attribute #[cfg()] has many possible values, e.g.

  • test -> Unit test
  • target_os = "linux"
  • target_arch = "aarch64"

The unit test is done.

XXX is never used

Quite certainly, functions and consts are going to be created specifically for some unit tests.
When building the application, you might face something quite annoying.

warning: constant `POST_DATA` is never used
 --> src/bin/post-create/test_data.rs:2:7
  |
2 | const POST_DATA: &str = r#"[ID]: # (bcfc427f-f9f3-4442-bfc2-deca95db96d5)
  |       ^^^^^^^^^
  |
  = note: `#[warn(dead_code)]` on by default
Enter fullscreen mode Exit fullscreen mode

Basically it means that this is not being used as the unit tests are not being compiled (the compiler conditional #[cfg(test)] is false when not building unit tests).

There are 2 simple ways to fix that.

  1. Add #[allow(dead_code)] before that const
  2. Add #[cfg(test)] before that const (my preferred)

E.g.

#[cfg(test)]
pub(crate) const POST_DATA: &str = r#"[ID]: # (bcfc427f-f9f3-4442-bfc2-deca95db96d5)



# This is a title
"#;
Enter fullscreen mode Exit fullscreen mode

Running tests

In the command line, run

cargo test
Enter fullscreen mode Exit fullscreen mode

That is all


Link to the post in the author's blog:
https://thiagocafe.com/view/20240226_unit_tests_in_rust/

Top comments (1)

Collapse
 
wangdengwu profile image
王登武

well