In this post I will go over how to write Unit test for your Rust application. We will continue with where we left off with the hello world program and see what Rust has to offer us in unit testing. Note: I will be Writing and Running Intergration Tests with Rust in VSCode.
Creating Rust test directory
1 2 3 |
#install the pre req $ mkdir tests $ touch tests/hello_tests.rs |
Our directory would look something like below.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
. ├── Cargo.lock ├── Cargo.toml ├── src │ └── main.rs ├── target │ ├── CACHEDIR.TAG │ ├── debug │ └── rls └── tests └── hello_tests.rs 5 directories, 5 files |
In our hellow_tests.rs file we will add the code like below, if you are coming from a C# or Java background, Rust also uses Attribute for test so it will look quite familiar, using # tag followed by [test] brackets. We are using assert! just to pass this test.
1 2 3 4 |
#[test] fn test_hello_output() { assert!(true); } |
To run the test we can run the cargo test command like below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
$ cargo test Compiling hello v0.1.0 (/home/taswar/hello) Finished test [unoptimized + debuginfo] target(s) in 1.51s Running unittests (target/debug/deps/hello-b89e93aae08f04d4) running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s Running tests/hello_tests.rs (target/debug/deps/hello_tests-8e52de7efe492fbd) running 1 test test test_hello_output ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s |
If you remember we had our program output a simple println statement with Hello, world!, so our test needs to check the output of our program. In order to do so we will introduce a new cargo package into your system.
Lets modify our project dependency and use crate assert_cmd to find the program in our crate directory.
Adding development dependency to Cargo.toml
Open the file Cargo.toml and add the dev dependency like below.
1 2 3 4 5 6 7 8 9 10 11 |
[package] name = "hello" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] [dev-dependencies] assert_cmd = "1" |
Now we will need to modify our hellow_tests.rs file we will add the usage of the assert_cmd package.
1 2 3 4 5 6 7 |
use assert_cmd::Command; #[test] fn test_hello_output() { let mut program = Command::cargo_bin("hello").unwrap(); program.assert().success().stdout("Hello, world!"); } |
Above you will see that I am using assert_cmd and have changed some of the code to use the Command library. In the above code you will also see I am using a strange variable naming “let mut”. You may wonder what that is?
let mut what is that?
So variables in Rust are immutable by default, and require the mut keyword to be made mutable here. I am also using cargo_bin so that it can automatically find the output of the hello world program.
Unwrap what is that?
You also see I am calling a function called unwrap, so unwrap() is used here to handle the errors quickly in our test. It can be used on any function that returns Result or Option (Option is also an enum). If the function returns an Ok(value), you will get the value. If the function returns an Err(error), the program/test will panic. In our case if it does not find the hello program it will be in panic mode.
Next I have used assert with success to find out the command was successful and trying to match the Hello, world! output. If we run this test we will see that it will fail.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
$ cargo test Finished test [unoptimized + debuginfo] target(s) in 0.03s Running unittests (target/debug/deps/hello-06d7198787a084e0) running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s Running tests/hello_tests.rs (target/debug/deps/hello_tests-5abafa31ab2a94f9) running 1 test test test_hello_output ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s taswar@IST11-800491469:~/hello$ cargo test Finished test [unoptimized + debuginfo] target(s) in 0.02s Running unittests (target/debug/deps/hello-06d7198787a084e0) running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s Running tests/hello_tests.rs (target/debug/deps/hello_tests-5abafa31ab2a94f9) running 1 test test test_hello_output ... FAILED failures: ---- test_hello_output stdout ---- thread 'test_hello_output' panicked at 'Unexpected stdout, failed diff original var ├── original: Hello, world! ├── diff: └── var as str: Hello, world! command=`"/home/taswar/hello/target/debug/hello"` code=0 stdout=```"Hello, world!\n"``` stderr=```""``` ', /home/taswar/.cargo/registry/src/github.com-1ecc6299db9ec823/assert_cmd-1.0.8/src/assert.rs:124:9 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace failures: test_hello_output test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s error: test failed, to rerun pass '--test hello_tests' |
One may wonder what is wrong with it, so basically what we are missing is the newline character in our test \n in our stdout, so lets modify our test code and add the newline character.
1 2 3 4 5 6 7 |
use assert_cmd::Command; #[test] fn test_hello_output() { let mut program = Command::cargo_bin("hello").unwrap(); program.assert().success().stdout("Hello, world!\n"); } |
Now the output of our test should be passing.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
cargo test Compiling hello v0.1.0 (/home/taswar/hello) Finished test [unoptimized + debuginfo] target(s) in 1.39s Running unittests (target/debug/deps/hello-06d7198787a084e0) running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s Running tests/hello_tests.rs (target/debug/deps/hello_tests-5abafa31ab2a94f9) running 1 test test test_hello_output ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s |
Summary
I understand the fact that this is a very brittle test checking the output of our program to match a certain string. The idea of this is for you to learn how to write simple unit test in Rust and how the ecosystem works. I hope this helps in your journey, we will continue on our learning Rust journey to write simple unix/linux command line tools and built upon that.
Leave A Comment