Testing CLIs
There is an extension module to insta called insta-cmd
which allows to you to easily test command line applications.
Basics
The two most important APIs in insta_cmd
are
assert_cmd_snapshot
and
get_cargo_bin
. The
first is macro which lets you assert the output of a Command
. The latter is a function that returns the path to a binary (the one from your crate).
In the most basic example you would end up with something like this:
use std::process::Command;
use insta_cmd::{assert_cmd_snapshot, get_cargo_bin};
fn cli() -> Command {
Command::new(get_cargo_bin("my-executable"))
}
#[test]
fn test_version() {
assert_cmd_snapshot!(cli().arg("--version"), @###"
success: true
exit_code: 0
----- stdout -----
my-executable 1.0.0
----- stderr -----
"###);
}
Passing Stdin
For when your application uses stdin, you can use the magic pass_stdin
method that is
also available within the macro:
use std::process::Command;
use insta_cmd::{assert_cmd_snapshot, get_cargo_bin};
fn cli() -> Command {
Command::new(get_cargo_bin("my-executable"))
}
#[test]
fn test_echo_back() {
assert_cmd_snapshot!(cli().arg("echo-back").pass_stdin("Hello World!"), @###"
success: true
exit_code: 0
----- stdout -----
Hello World!
----- stderr -----
"###);
}
Filtering
The most important aspect when working with command line tools is to use the filters
feature to redact output. This is because many command line tools use paths and other
things that make it otherwise hard to snapshot. It's recommended to use a reusable macro
to normalize this. Here an example of some filters:
macro_rules! apply_common_filters {
{} => {
let mut settings = insta::Settings::clone_current();
// Macos Temp Folder
settings.add_filter(r"/var/folders/\S+?/T/\S+", "[TEMP_FILE]"),
// Linux Temp Folder
settings.add_filter(r"/tmp/\.tmp\S+", "[TEMP_FILE]");
// Windows Temp folder
settings.add_filter(r"\b[A-Z]:\\.*\\Local\\Temp\\\S+", "[TEMP_FILE]");
// Convert windows paths to Unix Paths.
settings.add_filter(r"\\\\?([\w\d.])", "/$1");
let _bound = settings.bind_to_scope();
}
}
And then you can use it as such:
#[test]
fn test_basic() {
apply_common_filters();
assert_cmd_snapshot!(cli().arg("create-temp-file").arg("--template=./foo"), @###"
success: true
exit_code: 0
----- stdout -----
Created temp file in [TEMP_FILE]
Template file: ./foo
----- stderr -----
"###);
}
Examples
If you want to be inspired, have a look at the following projects for some ideas:
rye
: rye testsminijinja
: minijinja-cli tests