//! # Snapshot testing toolbox //! //! > When you have to treat your tests like pets, instead of [cattle][trycmd] //! //! `snapbox` is a snapshot-testing toolbox that is ready to use for verifying output from //! - Function return values //! - CLI stdout/stderr //! - Filesystem changes //! //! It is also flexible enough to build your own test harness like [trycmd](https://crates.io/crates/trycmd). //! //! ## Which tool is right //! //! - [cram](https://bitheap.org/cram/): End-to-end CLI snapshotting agnostic of any programming language //! - [trycmd](https://crates.io/crates/trycmd): For running a lot of blunt tests (limited test predicates) //! - Particular attention is given to allow the test data to be pulled into documentation, like //! with [mdbook](https://rust-lang.github.io/mdBook/) //! - `snapbox`: When you want something like `trycmd` in one off //! cases or you need to customize `trycmd`s behavior. //! - [assert_cmd](https://crates.io/crates/assert_cmd) + //! [assert_fs](https://crates.io/crates/assert_fs): Test cases follow a certain pattern but //! special attention is needed in how to verify the results. //! - Hand-written test cases: for peculiar circumstances //! //! ## Getting Started //! //! Testing Functions: //! - [`assert_eq`][crate::assert_eq] and [`assert_matches`] for reusing diffing / pattern matching for non-snapshot testing //! - [`assert_eq_path`][crate::assert_eq_path] and [`assert_matches_path`] for one-off assertions with the snapshot stored in a file //! - [`harness::Harness`] for discovering test inputs and asserting against snapshot files: //! //! Testing Commands: //! - [`cmd::Command`]: Process spawning for testing of non-interactive commands //! - [`cmd::OutputAssert`]: Assert the state of a [`Command`][cmd::Command]'s //! [`Output`][std::process::Output]. //! //! Testing Filesystem Interactions: //! - [`path::PathFixture`]: Working directory for tests //! - [`Assert`]: Diff a directory against files present in a pattern directory //! //! You can also build your own version of these with the lower-level building blocks these are //! made of. //! #![cfg_attr(feature = "document-features", doc = document_features::document_features!())] //! //! # Examples //! //! [`assert_matches`] //! ```rust //! snapbox::assert_matches("Hello [..] people!", "Hello many people!"); //! ``` //! //! [`Assert`] //! ```rust,no_run //! let actual = "..."; //! let expected_path = "tests/fixtures/help_output_is_clean.txt"; //! snapbox::Assert::new() //! .action_env("SNAPSHOTS") //! .matches_path(expected_path, actual); //! ``` //! //! [`harness::Harness`] #![cfg_attr(not(feature = "harness"), doc = " ```rust,ignore")] #![cfg_attr(feature = "harness", doc = " ```rust,no_run")] //! snapbox::harness::Harness::new( //! "tests/fixtures/invalid", //! setup, //! test, //! ) //! .select(["tests/cases/*.in"]) //! .action_env("SNAPSHOTS") //! .test(); //! //! fn setup(input_path: std::path::PathBuf) -> snapbox::harness::Case { //! let name = input_path.file_name().unwrap().to_str().unwrap().to_owned(); //! let expected = input_path.with_extension("out"); //! snapbox::harness::Case { //! name, //! fixture: input_path, //! expected, //! } //! } //! //! fn test(input_path: &std::path::Path) -> Result> { //! let raw = std::fs::read_to_string(input_path)?; //! let num = raw.parse::()?; //! //! let actual = num + 10; //! //! Ok(actual) //! } //! ``` //! //! [trycmd]: https://docs.rs/trycmd #![cfg_attr(docsrs, feature(doc_auto_cfg))] mod action; mod assert; mod data; mod error; mod substitutions; pub mod cmd; pub mod path; pub mod report; pub mod utils; #[cfg(feature = "harness")] pub mod harness; pub use action::Action; pub use action::DEFAULT_ACTION_ENV; pub use assert::Assert; pub use data::Data; pub use data::DataFormat; pub use data::{Normalize, NormalizeMatches, NormalizeNewlines, NormalizePaths}; pub use error::Error; pub use snapbox_macros::debug; pub use substitutions::Substitutions; pub type Result = std::result::Result; /// Check if a value is the same as an expected value /// /// When the content is text, newlines are normalized. /// /// ```rust /// let output = "something"; /// let expected = "something"; /// snapbox::assert_eq(expected, output); /// ``` #[track_caller] pub fn assert_eq(expected: impl Into, actual: impl Into) { Assert::new().eq(expected, actual); } /// Check if a value matches a pattern /// /// Pattern syntax: /// - `...` is a line-wildcard when on a line by itself /// - `[..]` is a character-wildcard when inside a line /// - `[EXE]` matches `.exe` on Windows /// /// Normalization: /// - Newlines /// - `\` to `/` /// /// ```rust /// let output = "something"; /// let expected = "so[..]g"; /// snapbox::assert_matches(expected, output); /// ``` #[track_caller] pub fn assert_matches(pattern: impl Into, actual: impl Into) { Assert::new().matches(pattern, actual); } /// Check if a value matches the content of a file /// /// When the content is text, newlines are normalized. /// /// ```rust,no_run /// let output = "something"; /// let expected_path = "tests/snapshots/output.txt"; /// snapbox::assert_eq_path(expected_path, output); /// ``` #[track_caller] pub fn assert_eq_path(expected_path: impl AsRef, actual: impl Into) { Assert::new() .action_env(DEFAULT_ACTION_ENV) .eq_path(expected_path, actual); } /// Check if a value matches the pattern in a file /// /// Pattern syntax: /// - `...` is a line-wildcard when on a line by itself /// - `[..]` is a character-wildcard when inside a line /// - `[EXE]` matches `.exe` on Windows /// /// Normalization: /// - Newlines /// - `\` to `/` /// /// ```rust,no_run /// let output = "something"; /// let expected_path = "tests/snapshots/output.txt"; /// snapbox::assert_matches_path(expected_path, output); /// ``` #[track_caller] pub fn assert_matches_path( pattern_path: impl AsRef, actual: impl Into, ) { Assert::new() .action_env(DEFAULT_ACTION_ENV) .matches_path(pattern_path, actual); } /// Check if a path matches the content of another path, recursively /// /// When the content is text, newlines are normalized. /// /// ```rust,no_run /// let output_root = "..."; /// let expected_root = "tests/snapshots/output.txt"; /// snapbox::assert_subset_eq(expected_root, output_root); /// ``` #[cfg(feature = "path")] #[track_caller] pub fn assert_subset_eq( expected_root: impl Into, actual_root: impl Into, ) { Assert::new() .action_env(DEFAULT_ACTION_ENV) .subset_eq(expected_root, actual_root); } /// Check if a path matches the pattern of another path, recursively /// /// Pattern syntax: /// - `...` is a line-wildcard when on a line by itself /// - `[..]` is a character-wildcard when inside a line /// - `[EXE]` matches `.exe` on Windows /// /// Normalization: /// - Newlines /// - `\` to `/` /// /// ```rust,no_run /// let output_root = "..."; /// let expected_root = "tests/snapshots/output.txt"; /// snapbox::assert_subset_matches(expected_root, output_root); /// ``` #[cfg(feature = "path")] #[track_caller] pub fn assert_subset_matches( pattern_root: impl Into, actual_root: impl Into, ) { Assert::new() .action_env(DEFAULT_ACTION_ENV) .subset_matches(pattern_root, actual_root); }