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:

Found an issue? You can edit this page on GitHub.