[Thiago Cafe] Programming is fun!

A simple guide to unit tests in Rust

Created by Thiago Guedes on 2024-02-27 06:15:19

Tags: #rust   #unittest  

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");

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)
[DATE]: # (2024-02-27 06:20:53.000)
[AUTHOR]: # (Thiago)

# This is a title

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
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.
    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.

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

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)


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

cargo test

That is all

Tell me your opinion!

Reach me on Twitter - @thiedri