Crate cli_test_dir
source ·Expand description
This crate makes it easier to write integration tests for CLI applications. It’s based on the “workdir” pattern used by BurntSushi’s xsv and ripgrep crates, but packaged in an easy-to-reuse form.
To use this crate, add the following lines to your Cargo.toml
file:
[dev-dependencies]
# You can replace "*" with the current version of this crate.
cli_test_dir = "*"
You should now be able to write tests in tests/tests.rs
as follows:
use cli_test_dir::*;
#[test]
fn write_output_file() {
let testdir = TestDir::new("myapp", "write_output_file");
testdir.cmd()
.arg("out.txt")
.expect_success();
testdir.expect_path("out.txt");
}
You can use any options from std::process::Command
to invoke
your program.
§Testing that the program ran successfully
To check that a command succeeds, we can write:
let testdir = TestDir::new("true", "true_succeeds");
let mut cmd = testdir.cmd();
cmd.expect_success();
But this test would fail:
// Fails.
let testdir = TestDir::new("false", "false_succeeds");
let testdir = TestDir::new("cmd", "false_succeeds");
let mut cmd = testdir.cmd();
cmd.expect_success();
§Testing that the program exited with an error.
Sometimes you want to test that a program fails to run successfully.
let testdir = TestDir::new("false", "false_fails");
let mut cmd = testdir.cmd();
cmd.expect_failure();
And as you would expect, this test would fail:
// Fails.
let testdir = TestDir::new("true", "true_fails");
let mut cmd = testdir.cmd();
cmd.expect_failure();
§File input and output
The src_path
function can be used to build paths relative to the top-level
of our crate, and expect_path
can be used to make sure an output file
exists:
let testdir = TestDir::new("cp", "cp_copies_files");
let mut cmd = testdir.cmd();
cmd
.arg(testdir.src_path("fixtures/input.txt"))
.arg("output.txt")
.expect_success();
testdir.expect_path("output.txt");
We can also create the input file manually or look for specific contents in the output file if we wish:
let testdir = TestDir::new("cp", "cp_copies_files_2");
let mut cmd = testdir.cmd();
testdir.create_file("input.txt", "Hello, world!\n");
cmd
.arg("input.txt")
.arg("output.txt")
.expect_success();
testdir.expect_contains("output.txt", "Hello");
testdir.expect_file_contents("output.txt", "Hello, world!\n");
There are also negative versions of these functions where useful:
let testdir = TestDir::new("cp", "negative_tests");
let mut cmd = testdir.cmd();
testdir.create_file("input.txt", "Hello, world!\n");
cmd
.arg("input.txt")
.arg("output.txt")
.expect_success();
testdir.expect_does_not_contain("output.txt", "Goodbye");
testdir.expect_no_such_path("does_not_exist.txt");
§Standard input and output
We can also test standard input and output:
let testdir = TestDir::new("cat", "cat_passes_data_through");
let mut cmd = testdir.cmd();
let output = cmd
.output_with_stdin("Hello\n")
.expect_success();
assert_eq!(output.stdout_str(), "Hello\n");
If you wish, you can display a command’s output using tee_output
:
let testdir = TestDir::new("cat", "tee_output_shows_output");
let mut cmd = testdir.cmd();
let output = cmd
.output_with_stdin("Hello\n")
// Show `stdout` and `stderr`.
.tee_output()
.expect_success();
assert_eq!(output.stdout_str(), "Hello\n");
Note that this will currently print out all of stdout
first, then all of
stderr
, instead of interleaving them normally.
To see the output of tee_output
, you will also need to invoke cargo
as
follows:
cargo test -- --nocapture
§Contributing
Your feedback and contributions are welcome! Please see GitHub for details.
Structs§
- This code is inspired by the
WorkDir
pattern that BurntSushi uses to test CLI tools likeripgrep
andxsv
.
Traits§
- Extension methods for
std::process::Command
. - We define
expect_status
on quite a few related types to support different calling patterns. - Extension methods for
std::process::Output
. - Display command output and return it for examination.