Crate cli_test_dir [] [src]

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 = "*"

Then add the following to the top of tests/tests.rs:

extern crate cli_test_dir;

You should now be able to write tests 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");
testdir.cmd().expect_success();

But this test would fail:

// Fails.
let testdir = TestDir::new("false", "false_succeeds");
testdir.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");
testdir.cmd().expect_failure();

And as you would expect, this test would fail:

// Fails.
let testdir = TestDir::new("true", "true_fails");
testdir.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");
testdir.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");
testdir.create_file("input.txt", "Hello, world!\n");
testdir.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");
testdir.create_file("input.txt", "Hello, world!\n");
testdir.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 output = testdir.cmd()
  .output_with_stdin("Hello\n")
  .expect_success();
assert_eq!(output.stdout_str(), "Hello\n");

Contributing

Your feedback and contributions are welcome! Please see GitHub for details.

Structs

TestDir

This code is inspired by the WorkDir pattern that BurntSushi uses to test CLI tools like ripgrep and xsv.

Traits

CommandExt

Extension methods for std::process::Command.

ExpectStatus

We define expect_status on quite a few related types to support different calling patterns.

OutputExt

Extension methods for std::process::Output.