Getting Started with Missouri
Missouri tests a CLI tool by modeling its behavior as a graph of filesystem states. A transition says that this command, run on these files, must produce those files. In this tutorial you build a two-state graph, watch it pass, add an assertion, and then watch it fail. Read What is Missouri? for the concepts behind the model.
Install missouri
Build missouri from source:
cargo install --locked --git https://github.com/cjohnhanson/missouriCheck that the binary is available:
missouri --versionInitialize a project
Create a directory and initialize the project:
mkdir my-project && cd my-project
missouri initThis command creates a .missouri/ directory that holds:
.missouri/
missouri.yml # project-level config (empty for now)
bin/ # shared scripts available on PATH during test runs
ignore # gitignore-syntax patterns to exclude from comparisonThe project-level missouri.yml can declare environment variables, setup commands, and nix packages. Keep the empty default for now.
Create two states
A state is a directory with a .missouri/missouri.yml file. The files in the directory are the expected filesystem at that point in the test.
Create the first state. It is an empty starting point:
missouri state add cleanThis command creates clean/.missouri/missouri.yml with an empty config ({}).
Now create the second state. It holds the files that must exist after the command runs:
missouri state add builtAdd the expected output file to the built state:
echo "hello" > built/output.txtThe directory tree now looks like:
my-project/
.missouri/
missouri.yml
bin/
ignore
clean/
.missouri/
missouri.yml
built/
.missouri/
missouri.yml
output.txtDefine a transition
Edit clean/.missouri/missouri.yml and declare the transition from the clean state to the built state:
transitions:
- name: "create output"
command: "echo hello > output.txt"
target: ../builtThe fields:
name. An optional label for the test output.command. The shell command to run. It runs throughsh -cby default.target. The relative path to the expected target state directory.
Missouri copies the source state’s files to a temp directory. It runs the command there. It then compares the result against the target state’s files. The transition passes when the two match.
Run the tests
missouri runThe output looks like this:
✓ [1/1]
PASS clean → built 14ms
1 passed, 0 failed, 1 step in 14msMissouri found two states and one transition from clean to built. It ran the command. It then confirmed that the resulting filesystem matched the built state.
Validate without running
Check that the graph is well-formed. This command runs nothing:
missouri validatevalid: 2 state(s), 1 transition(s), 1 root(s)List the test paths that missouri would run:
missouri list1. clean → built
1 path(s)Add an assertion
An assertion is a command that verifies a property of a state. It does not change the state. Missouri runs a target state’s assertions after each transition into it, and a source state’s assertions before the first transition out of it.
Edit built/.missouri/missouri.yml:
assertions:
- name: "output contains hello"
command: "cat output.txt"
stdout: "hello\n"The fields:
name. An optional label for the test output.command. The command to run in the state’s directory.stdout. The exact stdout to expect. The assertion fails when the actual output differs.
Run the tests again. Missouri prints a passing assertion only with -v:
missouri run -v✓ [1/1]
PASS clean → built 30ms
✓ clean → built (create output) 29ms
✓ assert: output contains hello 16ms
1 passed, 0 failed, 1 step, 1 assertion in 30msThe assertion ran after the transition. It verified the file contents.
See a test fail
Change the expected stdout to a wrong value. Edit built/.missouri/missouri.yml:
assertions:
- name: "output contains hello"
command: "cat output.txt"
stdout: "goodbye\n"Run:
missouri run✗ [1/1]
FAIL clean → built 22ms
✗ clean → built (create output) 22ms
✗ assert: output contains hello 11ms
stdout expected: "goodbye\n"
stdout actual: "hello\n"
0 passed, 1 failed, 1 step, 1 assertion in 22msMissouri shows the exact mismatch. Change the value back to "hello\n" and the suite passes again.
A filesystem mismatch works the same way. Missouri reports the diff when the command produces a file that the target state does not hold, or when the contents differ.
Next steps
- Add more states and chain the transitions into multi-step paths. Missouri finds every root-to-leaf path for you.
- Add
comparatorsto a transition to skip a volatile file or to run a custom diff command. Read the configuration format for the fullmissouri.ymlschema. - Add
envto a state or to the project config to set environment variables. - Put shared scripts in
.missouri/bin/. Missouri adds that directory to PATH during a test run. - Use
--verbose(-v) for detailed output. Use--keep-tempto read the temp directories that missouri creates.