Part of the reason we split our logic into a library crate is to be able to test it!
Let’s start by moving the logic for finding a title in a markdown file out to its own function: title_from_content
.
fn title_from_content(input: &str) -> Option<String> {
input.lines().find(|v| v.starts_with("# ")).map(
|line| line.trim_start_matches("# ").to_string(),
)
}
and then we can use that instead of our previous logic to find the document title.
let document_title = title.or_else(|| title_from_content(&contents));
Writing unit tests in the same file
Unit tests are fully supported by Cargo and can live in the same file as our library code.
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn title_from_empty_string() {
assert_eq!(title_from_content(""), None);
}
#[test]
fn title_from_content_string() {
assert_eq!(
title_from_content("# some title"),
Some("some title".to_string())
);
}
}
When we run cargo test
, specific flags are set that we can test for. Using the cfg
attribute on our tests
sub-module means that our tests will only be compiled when we’re building our tests and not when we’re building our binary in debug or release mode.
The module syntax in Rust isn’t tied to the filesystem, so writing mod tests
could refer to a tests.rs
file, but if we include curly braces after mod tests {}
, then we can define the module right there in the same file. This is very useful for tests, but can also be used for backwards compatibility in module paths.
Since tests
is a sub-module, we can bring all of the parent items into scope using super::*
. Because the tests
module is in the same file as the parent, this means we’re bringing the items defined in this file into scope for tests.
use super::*;
The #[test]
attribute marks regular functions as tests that will execute when running cargo test
.
Tests fail when they panic, and there’s a series of helper macros for testing different values and panic’ing if they’re different, like assert_eq!
.
Each function name will be the name of your test, so title_from_empty_string
is both the function we’ve written and the name of the test in the output.
❯ cargo test
Finished test [unoptimized + debuginfo] target(s) in 0.03s
Running unittests src/lib.rs (target/debug/deps/garden-531b700807a0ce1b)
running 2 tests
test tests::title_from_content_string ... ok
test tests::title_from_empty_string ... ok
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running unittests src/main.rs (target/debug/deps/garden-dd0d368687185699)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests garden
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Each test uses assert_eq
with some value on the left and another value on the right. Usually one of these is the function you’re testing and the other is a value you’re checking the output against.
This gives us 2 unit tests, 0 doc-tests, and 0 integration tests.