Skip to main content

Test with trycmd

Use trycmd to write snapshot tests for your CLI commands. Tests are defined as simple TOML files with expected output, making them easy to write and maintain.

Add trycmd to your project

Add trycmd as a dev dependency:

cargo add --dev trycmd

Create the test file

Create a test file at tests/cli.rs:

#[test]
fn cli_tests() {
trycmd::TestCases::new()
.case("tests/cmd/*.toml");
}

This tells trycmd to look for test cases in tests/cmd/.

Write your first test

Create tests/cmd/greet.toml:

bin.name = "my-cli"
args = ["greet", "World"]
status.code = 0

This test runs my-cli greet World and verifies it exits with code 0.

Run the test:

cargo test

Test stdout output

To verify the command's output, add a .stdout file with the same name:

tests/cmd/greet.toml:

bin.name = "my-cli"
args = ["greet", "World"]
status.code = 0

tests/cmd/greet.stdout:

Hello, World!

The test passes if the command's stdout matches the file exactly.

Test file system changes

For commands that create or modify files, use .in/ and .out/ directories:

tests/cmd/
├── init.toml
├── init.in/
│ └── (empty or initial files)
└── init.out/
├── config.toml
└── src/
└── main.rs

tests/cmd/init.toml:

bin.name = "my-cli"
args = ["init"]
fs.sandbox = true
status.code = 0

With fs.sandbox = true:

  • The test runs in a temporary directory
  • Files from .in/ are copied to the sandbox before running
  • After the command runs, the sandbox is compared to .out/

This is useful for testing commands that generate files or modify project structure.

Test error cases

Test that commands fail correctly:

tests/cmd/missing-arg.toml:

bin.name = "my-cli"
args = ["deploy"]
status.code = 2

tests/cmd/missing-arg.stderr:

error: the following required arguments were not provided:
<ENVIRONMENT>
...

Use ... to match any text (useful for help text that may change).

Update expected output

When your CLI's output changes, update the expected files:

TRYCMD=dump cargo test

This overwrites .stdout, .stderr, and .out/ files with the actual output. Review the changes before committing.

Project structure

A typical test setup looks like this:

my-cli/
├── src/
│ ├── main.rs
│ ├── commands.rs
│ └── commands/
│ └── greet.rs
├── tests/
│ ├── cli.rs
│ └── cmd/
│ ├── greet-default.toml
│ ├── greet-default.stdout
│ ├── greet-name.toml
│ ├── greet-name.stdout
│ ├── greet-help.toml
│ └── greet-help.stdout
└── Cargo.toml

See also