A simple guide to unit tests in Rust
Tags: rust, unittest
Published: 2024-02-27 07:29:56

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](http://gitlab.com/thiagomg/texted2)

```rust
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
}
```

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

```rust
pub(crate) const HEADER_DATA: &str = r#"[ID]: # (bcfc427f-f9f3-4442-bfc2-deca95db96d5)
[DATE]: # (2024-02-27 06:20:53.000)
[AUTHOR]: # (Thiago)

# This is a title
"#;
```

And now I am going to write my unit test

```rust
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);
    }
}
```

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.

```text
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
```

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.

```rust
#[cfg(test)]
pub(crate) const POST_DATA: &str = r#"[ID]: # (bcfc427f-f9f3-4442-bfc2-deca95db96d5)
[DATE]: # (2024-02-27 06:20:53.000)
[AUTHOR]: # (Thiago)

# This is a title
"#;
```

## Running tests

In the command line, run

```bash
cargo test
```

That is all

--- 

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