Testing Rust Command Line Tools With Assert Cmd Step by Step
Test a CLI end to end with assert_cmd and predicates: run the real binary, check the exit status, stdout and stderr, feed stdin and use temporary files.

Unit tests prove the parts work. They do not prove the program prints the right thing when run as tool --lines notes.txt, handles a missing file with exit code 1, or fails on a flag typo with a usage error. assert_cmd closes that gap by running the real binary and describing what it did. Aim for a few end-to-end tests and many unit tests, a split described in testing in Rust. It is the testing step of the command line tool series.
assert_cmd 2, predicates 3, tempfile 3, Rust 1.90.
Dependencies
[dev-dependencies]
assert_cmd = "2"
predicates = "3"
tempfile = "3"The tests
use assert_cmd::Command;
use predicates::prelude::*;
use std::fs;
#[test]
fn counts_lines_from_stdin() {
Command::cargo_bin("wc-lite")
.unwrap()
.arg("--lines")
.write_stdin("a\nb\nc\n")
.assert()
.success()
.stdout("3\n");
}
#[test]
fn reads_a_file() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("notes.txt");
fs::write(&path, "one two\nthree\n").unwrap();
Command::cargo_bin("wc-lite")
.unwrap()
.arg(&path)
.assert()
.success()
.stdout(predicate::str::contains("2 3"));
}
#[test]
fn missing_file_fails_with_a_message() {
Command::cargo_bin("wc-lite")
.unwrap()
.arg("does-not-exist.txt")
.assert()
.failure()
.code(1)
.stderr(predicate::str::contains("does-not-exist.txt"));
}Run cargo test. Each test spawns the binary, so they are slower than unit tests: expect tens of milliseconds each. The first checks piped input and exact stdout. The second shows a temp file fixture, deleted when dir drops. The third asserts the exit code and that the error names the file, which keeps users from getting a generic failure.
What to assert
| Check | Why |
|---|---|
--help exits 0 and mentions the tool | Catches broken argument definitions |
| Bad flag exits 2 | Confirms usage errors, set by clap |
| Normal run, exact stdout | The contract with scripts |
| Failure, exit code and stderr text | The contract with people |
Exact stdout comparisons are brittle for human formats. Prefer contains for text a person reads, and exact matches for machine formats.
Snapshots
For long output, compare against a stored snapshot instead of writing it into the test; see snapshot tests with insta.
Environment
Command inherits the environment of the test process. Set .env("NO_COLOR", "1"), .env_remove("MYTOOL_CONFIG") and .current_dir(dir.path()) so a developer's machine settings cannot change the result.
Limits
These tests cannot reach private functions, and they cannot test interactive behavior that needs a terminal. For prompts, use a pseudo terminal crate or keep the logic testable. The crate documentation is on docs.rs for assert_cmd, and the Command Line Applications in Rust book has a testing chapter.


