Loading page…
Rust walkthroughs
Loading page…
predicates crate improve readability of assertions in tests compared to plain assert! macros?The predicates crate provides a functional, composable approach to writing assertions that reads naturally and produces clear failure messages. Unlike raw assert! macros that require manual condition construction and error messages, predicates are self-documenting and automatically generate informative output when tests fail.
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_with_plain_assert() {
let result = calculate_something();
// Basic assertions are simple enough
assert!(result.success);
// But complex conditions become hard to read
assert!(result.value > 0 && result.value < 100);
// And failure messages are often unhelpful
assert!(result.success, "Expected success but got failure");
// You end up writing verbose messages
assert!(
result.value > 0,
"Expected value to be positive, but got {}",
result.value
);
// Comparisons require manual message construction
assert_eq!(result.count, 5, "Expected count to be 5");
}
}Plain assert! requires you to write both the condition and the error message.
use predicates::prelude::*;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_with_predicates() {
let result = calculate_something();
// Simple predicates read naturally
assert_that!(result.success, predicates::bool::is_true());
// Value is automatically shown on failure
assert_that!(result.value, predicates::num::gt(0));
assert_that!(result.value, predicates::num::lt(100));
// Multiple assertions can be clearer
assert_that!(result.count, predicates::ord::eq(5));
}
}Predicates express what you're checking, and failures explain what went wrong.
# Cargo.toml
[dev-dependencies]
predicates = "3"// The prelude brings in common traits and macros
use predicates::prelude::*;
// Or import specific modules
use predicates::{boolean::PredicateBooleanExt, num, str};use predicates::prelude::*;
#[test]
fn numeric_predicates() {
let value = 42;
// Comparison predicates
assert_that!(value, predicates::gt(40)); // greater than
assert_that!(value, predicates::lt(50)); // less than
assert_that!(value, predicates::ge(42)); // greater or equal
assert_that!(value, predicates::le(42)); // less or equal
assert_that!(value, predicates::eq(42)); // equal
// These produce clear error messages:
// assertion failed: `42 gt 50`
// expected: 50
// actual: 42
}Numeric predicates make comparisons self-documenting.
use predicates::prelude::*;
#[test]
fn string_predicates() {
let text = "Hello, World!";
// Exact match
assert_that!(text, predicates::str::is_empty().not());
// Contains substring
assert_that!(text, predicates::str::contains("World"));
// Starts with / ends with
assert_that!(text, predicates::str::starts_with("Hello"));
assert_that!(text, predicates::str::ends_with("!"));
// Pattern matching
assert_that!(text, predicates::str::is_match(r"Hello, \w+!").unwrap());
// Case-insensitive comparison
assert_that!(text, predicates::str::similar("hello, world!"));
}String predicates provide expressive matching without regex complexity.
use predicates::prelude::*;
#[test]
fn combining_predicates() {
let value = 42;
// And: both must be true
assert_that!(
value,
predicates::gt(0).and(predicates::lt(100))
);
// Or: at least one must be true
assert_that!(
value,
predicates::eq(42).or(predicates::eq(0))
);
// Not: negate a predicate
assert_that!(value, predicates::eq(0).not());
// Complex combinations
assert_that!(
value,
predicates::gt(0)
.and(predicates::lt(50))
.and(predicates::eq(42).not())
);
}Predicates compose naturally with and, or, and not.
use predicates::prelude::*;
#[test]
fn combinator_examples() {
let value = 42;
// Chain multiple conditions
let in_range = predicates::gt(0).and(predicates::lt(100));
assert_that!(value, in_range);
// Store predicates for reuse
let is_valid = predicates::gt(0)
.and(predicates::lt(100))
.and(predicates::ne(50)); // not exactly 50
assert_that!(value, is_valid);
// Use in multiple tests
assert_that!(55, is_valid);
assert_that!(25, is_valid);
}Predicates can be stored and reused across tests.
use predicates::prelude::*;
#[test]
fn collection_predicates() {
let items = vec![1, 2, 3, 4, 5];
// Check if collection contains element
assert_that!(items.clone(), predicates::iter::contains(3));
// Check if collection contains a specific element
assert_that!(&items, predicates::iter::contains(&3));
// Check collection properties
assert_that!(items.clone(), predicates::iter::has_count(5));
// Check if all elements match a predicate
assert_that!(
items.clone(),
predicates::iter::all(predicates::gt(0))
);
// Check if any element matches
assert_that!(
items.clone(),
predicates::iter::any(predicates::eq(5))
);
}Collection predicates work with any iterable.
use predicates::prelude::*;
#[test]
fn option_predicates() {
let some_value: Option<i32> = Some(42);
let none_value: Option<i32> = None;
// Check if Some
assert_that!(some_value, predicates::option::some(predicates::gt(40)));
// Check if None
assert_that!(none_value, predicates::option::none());
// Check the contained value
assert_that!(some_value, predicates::option::some(predicates::eq(42)));
}
#[test]
fn result_predicates() {
let ok_value: Result<i32, &str> = Ok(42);
let err_value: Result<i32, &str> = Err("failed");
// Check if Ok
assert_that!(ok_value, predicates::result::ok(predicates::eq(42)));
// Check if Err
assert_that!(err_value, predicates::result::err(predicates::eq("failed")));
}Option and Result predicates check both the variant and contained value.
use predicates::prelude::*;
use std::path::Path;
#[test]
fn path_predicates() {
let path = Path::new("/some/path/to/file.txt");
// Check existence
assert_that!(path, predicates::path::exists());
// Check file properties
assert_that!(path, predicates::path::is_file());
// Check extension
assert_that!(path, predicates::path::has_extension("txt"));
// Check basename
assert_that!(path, predicates::str::ends_with("file.txt"));
}Path predicates are useful for filesystem-related tests.
use predicates::prelude::*;
use predicates::Predicate;
// Define a custom predicate using a closure
fn is_even() -> impl Predicate<i32> {
predicates::function::function(|x: &i32| *x % 2 == 0)
.fn_name("is_even")
}
#[test]
fn custom_predicate() {
assert_that!(4, is_even());
assert_that!(6, is_even());
// This would fail with a clear message:
// assert_that!(5, is_even());
// assertion failed: `is_even`
// actual: 5
}Custom predicates encapsulate complex logic.
use predicates::prelude::*;
#[test]
fn float_predicates() {
let value = 3.14159;
// Approximate equality for floats
assert_that!(value, predicates::float::close_to(3.14, 0.01));
// Not close to
assert_that!(value, predicates::float::close_to(3.0, 0.01).not());
// Handle edge cases
assert_that!(f64::INFINITY, predicates::float::is_infinite());
assert_that!(f64::NAN, predicates::float::is_nan());
}Float predicates handle floating-point comparison properly.
use predicates::prelude::*;
#[test]
fn comparison_plain_vs_predicates() {
let result = Some(42);
// Plain assert - verbose and unclear on failure
assert!(
result.is_some() && result.unwrap() > 40,
"Expected Some(value > 40), got {:?}",
result
);
// Predicates - clear and self-documenting
assert_that!(
result,
predicates::option::some(predicates::gt(40))
);
// Failure message from predicates:
// assertion failed
// expected: Some(greater than 40)
// actual: Some(35)
}Predicates produce better error messages automatically.
use predicates::prelude::*;
#[test]
fn debug_output() {
let value = 42;
// When this assertion fails:
assert_that!(value, predicates::gt(100));
// You get clear output:
// assertion failed
// expected: greater than 100
// actual: 42
let text = "hello";
assert_that!(text, predicates::str::starts_with("world"));
// Output:
// assertion failed
// expected: starts with "world"
// actual: "hello"
}Failure messages explain both what was expected and what was found.
use predicates::prelude::*;
// Works with any test framework that supports panics on failure
#[test]
fn standard_test_framework() {
// Works with #[test]
assert_that!(42, predicates::gt(0));
}
// Works with cargo test output
#[test]
#[should_panic(expected = "assertion failed")]
fn expected_failure() {
assert_that!(0, predicates::gt(0));
}Predicates work with Rust's built-in test framework.
use predicates::prelude::*;
#[test]
fn user_validation() {
let user = create_user("alice", "alice@example.com");
// Predicates read like documentation
assert_that!(user.username, predicates::str::starts_with("a"));
assert_that!(user.email, predicates::str::contains("@"));
assert_that!(user.age, predicates::gt(0).and(predicates::lt(150)));
assert_that!(user.active, predicates::bool::is_true());
// Compare to plain asserts:
// assert!(user.username.starts_with("a"));
// assert!(user.email.contains("@"));
// assert!(user.age > 0 && user.age < 150);
// assert!(user.active);
}Predicate-based tests serve as readable documentation.
use predicates::prelude::*;
#[test]
fn transforming_values() {
let result = " Hello, World! ";
// Map the value before testing
assert_that!(
result,
predicates::str::trim().eq("Hello, World!")
);
// Compare to plain assert:
// assert_eq!(result.trim(), "Hello, World!");
}Transformations allow testing derived values.
use predicates::prelude::*;
#[test]
fn boolean_predicates() {
let flag = true;
// Direct boolean check
assert_that!(flag, predicates::bool::is_true());
assert_that!(flag, predicates::bool::is_false().not());
// More readable than:
// assert!(flag);
// assert!(!flag);
}Boolean predicates are explicit about expectations.
use predicates::prelude::*;
struct User {
name: String,
age: u32,
email: String,
}
#[test]
fn structured_assertions() {
let user = User {
name: "Alice".to_string(),
age: 30,
email: "alice@example.com".to_string(),
};
// Instead of:
// assert_eq!(user.name, "Alice");
// assert!(user.age > 18);
// assert!(user.email.contains("@"));
// Use:
assert_that!(&user.name, predicates::eq("Alice"));
assert_that!(user.age, predicates::gt(18));
assert_that!(&user.email, predicates::str::contains("@"));
}Predicates make structured assertions consistent.
The predicates crate improves test readability and maintainability through:
Composability:
// Complex conditions become readable chains
assert_that!(value, predicates::gt(0).and(predicates::lt(100)));Self-documenting assertions:
// Intent is clear from the predicate name
assert_that!(email, predicates::str::contains("@"));Automatic error messages:
// Failures explain what was expected vs actual
// assertion failed
// expected: contains "@"
// actual: "invalid-email"Reusability:
// Predicates can be stored and reused
let is_valid = predicates::gt(0).and(predicates::lt(100));
assert_that!(value, is_valid);Key comparison:
| Plain assert! | predicates |
|----------------|--------------|
| assert!(x > 0) | assert_that!(x, predicates::gt(0)) |
| assert!(x > 0 && x < 100) | assert_that!(x, predicates::gt(0).and(predicates::lt(100))) |
| assert!(s.contains("x")) | assert_that!(s, predicates::str::contains("x")) |
| Manual error messages | Automatic descriptions |
Use predicates when test readability matters, especially for complex conditions or when you want clear failure messages without manual message construction.