2 releases
0.0.2 | Jan 18, 2024 |
---|---|
0.0.1 | Jan 15, 2024 |
#201 in Testing
485KB
9K
SLoC
Table of content
- Introducing clearcheck
- Key features
- Usage
- Assertions vs Matchers
- Supported assertions
- Changelog
- Unleashing the power of custom matchers and assertions
- Rust features
- Example project
- Reference
- Mention
Introducing clearcheck
Write expressive and elegant assertions with ease!
clearcheck is designed to make assertion statements as clear and concise as possible. It allows chaining multiple assertions together for a fluent and intuitive syntax, leading to more self-documenting test cases.
let pass_phrase = "P@@sw0rd1 zebra alpha";
pass_phrase.should_not_be_empty()
.should_have_at_least_length(10)
.should_contain_all_characters(vec!['@', ' '])
.should_contain_a_digit()
.should_not_contain_ignoring_case("pass")
.should_not_contain_ignoring_case("word");
Key features:
🔹 Fluent API: Chain assertions for a natural and readable experience.
🔹 Extensive assertions: Variety of assertions covering common validation needs.
🔹 Customizable: Extend with your own assertions for specific domain requirements.
🔹 Type-safe: Built with Rust's type system for reliable and expressive assertions.
🔹 Custom assertions: Craft assertions tailored to your exact needs, ensuring comprehensive validations for various data structures.
Usage
Add this to your Cargo.toml
(no features):
[dev-dependencies]
clearcheck = { version = "0.0.2" }
Add this to your Cargo.toml
(all features):
[dev-dependencies]
clearcheck = { version = "0.0.2", features = ["num", "date", "regex", "file"] }
chrono = { version = "0.4.31" }
num = { version = "0.4.1" }
regex = { version = "1.10.2" }
walkdir = { version = "2.4.0", features = [] }
Assertions vs Matchers
Assertions serve as the cornerstone of the test cases, defining the exact expectations the code must fulfill. They act as a contract, ensuring that each data type (/data structure) adheres to its intended behavior.
Matchers, on the other hand, provide the granular tools for carrying out these assertions. They examine data and verify that the data conforms to specific criteria.
In essence, assertions orchestrate the high-level validation logic, while matchers act as the code-level inspectors, ensuring every detail aligns with the expectations.
Supported assertions
Bool
Assertions
Assertion | Description |
---|---|
should_be_true | Asserts that the boolean evaluates to true. |
should_be_false | Asserts that the boolean evaluates to false. |
Usage
let value = true;
value.should_be_true();
Char
Assertions
Assertion | Description |
---|---|
should_be_in_inclusive_range | Asserts that the character falls within the given inclusive range. |
should_not_be_in_inclusive_range | Asserts that the character does not fall within the given inclusive range. |
should_be_in_exclusive_range | Asserts that the character falls within the given exclusive range. |
should_not_be_in_exclusive_range | Asserts that the character does not fall within the given exclusive range. |
should_be_equal_ignoring_case | Asserts that the character equals other character, with case ignored. |
should_not_be_equal_ignoring_case | Asserts that the character does not equal other character, with case ignored. |
Usage
let letter = 'd';
letter.should_be_in_inclusive_range('a'..='d');
let letter = 'D';
letter.should_be_equal_ignoring_case('d');
Collections (Vector, Arrays, Slices)
Assertions
Assertion | Description |
---|---|
should_have_upper_bound | Asserts that all elements in the collection are less than or equal to the given element. |
should_have_lower_bound | Asserts that all elements in the collection are greater than or equal to the given element. |
should_contain_duplicates | Asserts that the collection contains atleast one duplicate element. |
should_not_contain_duplicates | Asserts that the collection does not contain any duplicate element. |
should_be_equal_ignoring_case | Asserts that the elements in the collection are equal to those in other, ignoring case differences. (Only applicable where elements can be represented as strings). |
should_not_be_equal_ignoring_case | Asserts that the elements in the collection are not equal to those in other, ignoring case differences. (Only applicable where elements can be represented as strings). |
should_be_monotonically_increasing | Asserts that the elements in the collection are in non-decreasing order (allowing consecutive equal elements). |
should_be_monotonically_decreasing | Asserts that the elements in the collection are in non-increasing order (allowing consecutive equal elements). |
should_be_strictly_increasing | Asserts that the elements in the collection are in strictly increasing order (no consecutive elements can be equal). |
should_be_strictly_decreasing | Asserts that the elements in the collection are in strictly decreasing order (no consecutive elements can be equal). |
should_contain | Asserts that the collection contains the given element. |
should_not_contain | Asserts that the collection does not contain the given element. |
should_contain_all | Asserts that the collection contains all the given elements. |
should_not_contain_all | Asserts that the collection does not contain all the given elements. |
should_contain_any | Asserts that the collection contains any of the given elements. |
should_not_contain_any | Asserts that the collection does not contain any of the given elements. |
should_be_empty | Asserts that the collection is empty. |
should_not_be_empty | Asserts that the collection is not empty. |
Size based assertions
Assertion | Description |
---|---|
should_have_size | Asserts that the size of the underlying collection is exactly the given size. |
should_not_have_size | Asserts that the size of the underlying collection is not the given size. |
should_have_at_least_size | Asserts that the size of the underlying collection is greater than or equal to the given size. |
should_have_at_most_size | Asserts that the size of the underlying collection is less than or equal to the given size. |
should_be_same_size_as | Asserts that the size of the underlying collection is same as that of the given collection. |
should_have_size_in_inclusive_range | Asserts that the size of the underlying collection falls within the given inclusive range. |
should_not_have_size_in_inclusive_range | Asserts that the size of the underlying collection does not fall within the given inclusive range. |
should_have_size_in_exclusive_range | Asserts that the size of the underlying collection falls within the given exclusive range. |
should_not_have_size_in_exclusive_range | Asserts that the size of the underlying collection does not fall within the given exclusive range. |
Usage
let keywords = ["testing", "automation", "clearcheck", "junit"];
keywords.should_not_be_empty()
.should_have_size_in_inclusive_range(4..=10)
.should_not_contain_duplicates()
.should_contain_any(vec!["junit", "clearcheck", "testing"])
.should_not_contain_any(vec!["scalatest", "gotest"]);
Date (enabled by 'date' feature, depends on chrono)
Assertions
Assertion | Description |
---|---|
should_have_same_year_as | Asserts that the date has the same year as the other date. |
should_not_have_same_year_as | Asserts that the date does not have the same year as the other date. |
should_have_year | Asserts that the date has the same year as the given year. |
should_not_have_year | Asserts that the date does not have the same year as the given year. |
should_have_same_month_as | Asserts that the date has the same month as the other date. |
should_not_have_same_month_as | Asserts that the date does not have the same month as the other date. |
should_have_month | Asserts that the date has the same month as the given month. |
should_not_have_month | Asserts that the date does not have the same month as the given month. |
should_have_same_day_as | Asserts that the date has the same day as the other date. |
should_not_have_same_day_as | Asserts that the date does not have the same day as the other date. |
should_have_day | Asserts that the date has the same day as the given day. |
should_not_have_day | Asserts that the date does not have the same day as the given day. |
should_be_a_leap_year | Asserts that the date falls in a leap year. |
should_not_be_a_leap_year | Asserts that the date does not fall in a leap year. |
Usage
use chrono::NaiveDate;
let date = NaiveDate::from_ymd_opt(2024, 1, 10).unwrap();
date
.should_be_a_leap_year()
.should_have_month(1)
.should_be_greater_than(&NaiveDate::from_ymd_opt(2023, 1, 10).unwrap());
Filepath (enabled by 'file' feature, depends on walkdir)
Assertions
Assertion | Description |
---|---|
should_be_a_directory | Asserts that the path is a directory. |
should_be_a_file | Asserts that the path is a file. |
should_be_a_symbolic_link | Asserts that the path is a symbolic link. |
should_be_zero_sized | Asserts that the path corresponds to a zero sized file. |
should_not_be_zero_sized | Asserts that the path corresponds to a non-zero sized file. |
should_be_readonly | Asserts that the path corresponds to a readonly file. |
should_be_writable | Asserts that the path corresponds to a writable file. |
should_be_absolute | Asserts that the path is absolute. |
should_be_relative | Asserts that the path is relative. |
should_have_extension | Asserts that the path corresponds to a file with the given extension. |
should_not_have_extension | Asserts that the path corresponds to a file that does not have the given extension. |
should_contain_file_name | Asserts that the path corresponds to a directory that contains the given file name. |
should_not_contain_file_name | Asserts that the path corresponds to a directory that does not contain the given file name. |
should_contain_all_file_names | Asserts that the path corresponds to a directory that contains all the given file names. |
should_not_contain_all_file_names | Asserts that the path corresponds to a directory that does not contain all the given file names. |
should_contain_any_of_file_names | Asserts that the path corresponds to a directory that contains any of the given file names. |
should_not_contain_any_of_file_names | Asserts that the path corresponds to a directory that does not contain any of the given file names. |
Usage
use tempdir::TempDir;
let temporary_directory = TempDir::new(".").unwrap();
let file_path_junit = temporary_directory.path().join("junit.txt");
let file_path_clearcheck = temporary_directory.path().join("clearcheck.txt");
let _ = File::create(file_path_junit).unwrap();
let _ = File::create(file_path_clearcheck).unwrap();
let directory_path = temporary_directory.path();
directory_path
.should_be_a_directory()
.should_contain_any_of_file_names(vec!["junit.txt", "clearcheck.txt"]);
Float (enabled by 'num' feature, depends on num)
Assertions
Assertion | Description |
---|---|
should_be_nan | Asserts that the floating-point value is NaN (Not a Number). |
should_not_be_nan | Asserts that the floating-point value is not NaN (Not a Number). |
should_be_zero | Asserts that the floating-point value is zero. |
should_not_be_zero | Asserts that the floating-point value is not zero. |
should_be_positive | Asserts that the floating-point value is positive. |
should_be_negative | Asserts that the floating-point value is negative. |
should_be_in_inclusive_range_with_tolerance | Asserts that the floating-point value falls within the given inclusive range with tolerance. |
should_not_be_in_inclusive_range_with_tolerance | Asserts that the floating-point value does not fall within the given inclusive range with tolerance. |
should_be_in_exclusive_range_with_tolerance | Asserts that the floating-point value falls within the given exclusive range with tolerance. |
should_not_be_in_exclusive_range_with_tolerance | Asserts that the floating-point value does not fall within the given exclusive range with tolerance. |
Usage
let value: f64 = 1.34589;
value
.should_not_be_nan()
.should_be_positive()
.should_be_in_inclusive_range_with_tolerance(1.11..=1.3458, 0.23);
Integer (enabled by 'num' feature, depends on num)
Assertions
Assertion | Description |
---|---|
should_be_positive | Asserts that the integer value is positive. |
should_be_negative | Asserts that the integer value is negative. |
should_be_even | Asserts that the integer value is even. |
should_be_odd | Asserts that the integer value is odd. |
should_be_zero | Asserts that the integer value is zero. |
should_not_be_zero | Asserts that the integer value is not zero. |
Usage
let value = 24;
value
.should_be_positive()
.should_be_even()
.should_be_in_inclusive_range(10..=40);
HashMap
Assertions
Assertion | Description |
---|---|
should_contain_key | Asserts that the HashMap contains the given key. |
should_not_contain_key | Asserts that the HashMap does not contain the given key. |
should_contain_all_keys | Asserts that the HashMap contains all the given keys. |
should_not_contain_all_keys | Asserts that the HashMap does not contain all the given keys. |
should_contain_any_of_keys | Asserts that the HashMap contains any of the given keys. |
should_not_contain_any_of_keys | Asserts that the HashMap does not contain any of the given keys. |
should_contain_value | Asserts that the HashMap contains the given value. |
should_not_contain_value | Asserts that the HashMap does not contain the given value. |
should_contain_all_values | Asserts that the HashMap contains all the given values. |
should_not_contain_all_values | Asserts that the HashMap does not contain all the given values. |
should_contain_any_of_values | Asserts that the HashMap contains any of the given values. |
should_not_contain_any_of_values | Asserts that the HashMap does not contain any of the given values. |
should_contain | Asserts that the HashMap contains the given key and the value. |
should_not_contain | Asserts that the HashMap does not contain the given key and the value. |
should_contain_all | Asserts that the HashMap contains all the entries from the given HashMap. |
should_not_contain_all | Asserts that the HashMap does not contain all the entries from the given HashMap. |
should_contain_any | Asserts that the HashMap contains any of the entries from the given HashMap. |
should_not_contain_any | Asserts that the HashMap does not contain any of the entries from the given HashMap. |
should_be_empty | Asserts that the HashMap is empty. |
should_not_be_empty | Asserts that the HashMap is not empty. |
+ | Size based assertions. |
Usage
#[derive(Eq, Debug, PartialEq, Hash)]
struct Book {
id: usize,
title: &'static str,
}
impl Book {
fn new(id: usize, title: &'static str) -> Self {
Book { id, title }
}
}
let mut book_id_by_name = HashMap::new();
book_id_by_name.insert("Database internals", 1);
book_id_by_name.insert("Designing data intensive applications", 2);
book_id_by_name
.should_not_be_empty()
.should_contain_key("Database internals")
.should_contain_value(&1)
.should_have_at_least_size(2)
.should_contain("Database internals", &1);
Option
Assertions
Assertion | Description |
---|---|
should_be_some | Asserts that the Option evaluates to Some. |
should_be_none | Asserts that the Option evaluates to None. |
Usage
let option = Some("clearcheck");
option.should_be_some();
Result
Assertions
Assertion | Description |
---|---|
should_be_ok | Asserts that the Result evaluates to Ok. |
should_be_err | Result evaluates to Err. |
Usage
let value: Result<i32, &str> = Ok(32);
value.should_be_ok();
T: PartialOrd
Assertions
Assertion | Description |
---|---|
should_be_greater_than | Asserts that the self value is greater than the given value (other) according to the PartialOrd implementation. |
should_be_greater_than_equal_to | Asserts that the self value is greater than or equal to the given value (other) according to the PartialOrd implementation. |
should_be_less_than | Asserts that the self value is less than the given value (other) according to the PartialOrd implementation. |
should_be_less_than_equal_to | Asserts that the self value is less than or equal to the given value (other) according to the PartialOrd implementation. |
should_not_be_greater_than | Asserts that the self value is not greater than the given value (other) according to the PartialOrd implementation. |
should_not_be_greater_than_equal_to | Asserts that the self value is not greater than or equal to the given value (other) according to the PartialOrd implementation. |
should_not_be_less_than | Asserts that the self value is not less than the given value (other) according to the PartialOrd implementation. |
should_not_be_less_than_equal_to | Asserts that the self value is not less than or equal to the given value (other) according to the PartialOrd implementation. |
should_be_in_inclusive_range | Asserts that the self value falls within the given inclusive range. |
should_not_be_in_inclusive_range | Asserts that the self value does not fall within the given inclusive range. |
should_be_in_exclusive_range | Asserts that the self value falls within the given exclusive range. |
should_not_be_in_exclusive_range | Asserts that the self value does not fall within the given exclusive range. |
Usage
let value = 12.56;
value
.should_be_greater_than(&10.90)
.should_be_less_than(&15.98)
.should_be_in_inclusive_range(10.90..=13.10);
T: Eq
Assertions
Assertion | Description |
---|---|
should_equal | Asserts that the value held by self is equal to other. |
should_not_equal | Asserts that the value held by self is not equal to other. |
Usage
#[derive(Debug, Eq, PartialEq)]
struct Book { name: &'static str }
let books = vec![
Book {name: "Database internals"},
Book {name: "Rust in action"}
];
let other = vec![
Book {name: "Database internals"},
Book {name: "Rust in action"}
];
books.should_equal(&other);
String
Assertions
Assertion | Description |
---|---|
should_begin_with | Asserts that the string begins with the given prefix. |
should_not_begin_with | Asserts that the string does not begin with the given prefix |
should_end_with | Asserts that the string ends with the given suffix. |
should_not_end_with | Asserts that the string does not end with the given suffix. |
should_be_lower_case | Asserts that the string is lowercase. |
should_be_upper_case | Asserts that the string is uppercase. |
should_be_equal_ignoring_case | Asserts that the string equals other string, with case ignored. |
should_not_be_equal_ignoring_case | Asserts that the string does not equal other string, with case ignored. |
should_only_contain_digits | Asserts that the string contains only digits. |
should_contain_a_digit | Asserts that the string contains a digit. |
should_not_contain_digits | Asserts that the string does not contain any digits. |
should_contain_character | Asserts that the string contains the given character. |
should_not_contain_character | Asserts that the string does not contain the given character. |
should_contain_all_characters | Asserts that the string contains all the given characters. |
should_not_contain_all_characters | Asserts that the string does not contain all the given characters. |
should_contain_any_characters | Asserts that the string contains any of the given characters. |
should_not_contain_any_characters | Asserts that the string does not contain any of the given characters. |
should_contain | Asserts that the string contains the given substring. |
should_not_contain | Asserts that the string does not contain the given substring. |
should_contain_ignoring_case | Asserts that the string contains the substring, ignoring case differences. |
should_not_contain_ignoring_case | Asserts that the string does not contain the substring, ignoring case differences. |
should_be_empty | Asserts that the string is empty (has zero characters). |
should_not_be_empty | Asserts that the string is not empty. |
should_be_numeric | Asserts that the string is numeric. |
should_not_be_numeric | Asserts that the string is not numeric. |
should_match | Asserts that the string matches the given regular expression. (enabled by 'regex' feature, depends on regex) |
should_not_match | Asserts that the string does not match the given regular expression. (enabled by 'regex' feature, depends on regex) |
Length based assertions
Assertion | Description |
---|---|
should_have_length | Asserts that the length of the string is exactly the given length. |
should_not_have_length | Asserts that the length of the string is not the given length. |
should_have_at_least_length | Asserts that the length of the string is greater than or equal to the given length. |
should_have_at_most_length | Asserts that the length of the string is less than or equal to the given length. |
should_have_length_in_inclusive_range | Asserts that the length of the string falls within the given inclusive range. |
should_not_have_length_in_inclusive_range | Asserts that the length of the string does not fall within the given inclusive range. |
should_have_length_in_exclusive_range | Asserts that the length of the string falls within the given exclusive range. |
should_not_have_length_in_exclusive_range | Asserts that the length of the string does not fall within the given exclusive range. |
Usage
let pass_phrase = "P@@sw0rd1 zebra alpha";
pass_phrase.should_not_be_empty()
.should_have_at_least_length(10)
.should_contain_all_characters(vec!['@', ' '])
.should_contain_a_digit()
.should_not_contain_ignoring_case("pass")
.should_not_contain_ignoring_case("word");
Changelog
Version 0.0.2
Version 0.0.2 refactored the String assertions to remove the duplication and introduced the following assertions:
Collection (Predicate based assertions)
Assertion | Description |
---|---|
should_satisfy_for_all | Asserts that all the elements in the collection satisfy the given predicate. |
should_not_satisfy_for_all | Asserts that not all the elements in the collection satisfy the given predicate. |
should_satisfy_for_any | Asserts that any of the elements in the collection satisfy the given predicate. |
should_not_satisfy_for_any | Asserts that none of the elements in the collection satisfy the given predicate. |
Collection (Min-max based assertions)
Assertion | Description |
---|---|
should_have_min | Asserts that the minimum value in the underlying collection equals the given minimum value. |
should_not_have_min | Asserts that the minimum value in the underlying collection does not equal the given minimum value. |
should_have_max | Asserts that the maximum value in the underlying collection equals the given maximum value. |
should_not_have_max | Asserts that the maximum value in the underlying collection does not equal the given maximum value. |
should_have_min_in_inclusive_range | Asserts that the minimum value in the underlying collection falls within the given inclusive range. |
should_not_have_min_in_inclusive_range | Asserts that the minimum value in the underlying collection does not fall within the given inclusive range. |
should_have_min_in_exclusive_range | Asserts that the minimum value in the underlying collection falls within the given exclusive range. |
should_not_have_min_in_exclusive_range | Asserts that the minimum value in the underlying collection does not fall within the given exclusive range. |
should_have_max_in_inclusive_range | Asserts that the maximum value in the underlying collection falls within the given inclusive range. |
should_not_have_max_in_inclusive_range | Asserts that the maximum value in the underlying collection does not fall within the given inclusive range. |
should_have_max_in_exclusive_range | Asserts that the maximum value in the underlying collection falls within the given exclusive range. |
should_not_have_max_in_exclusive_range | Asserts that the maximum value in the underlying collection does not fall within the given exclusive range. |
Option (Predicate based assertions)
Assertion | Description |
---|---|
should_be_some_and_satisfy | Asserts that the Option value is Some and satisfies the given predicate. |
should_be_some_and_not_satisfy | Asserts that the Option value is Some and does not satisfy the given predicate. |
Result (Predicate based assertions)
Assertion | Description |
---|---|
should_be_ok_and_satisfy | Asserts that the Result value is Ok and satisfies the given predicate. |
should_be_ok_and_not_satisfy | Asserts that the Result value is Ok and does not satisfy the given predicate. |
Unleashing the power of custom matchers and assertions
While this crate comes loaded with a plethora of ready-made assertions, sometimes your testing needs demand a bespoke touch. clearcheck allows crafting your own custom matchers and assertions!
The possibilities are endless:
- Domain-specific validation: Craft assertions that understand the nuances of your business logic.
- Enhanced readability: Write clear and concise matchers that mirror your domain vocabulary, making your tests self-documenting and understandable.
- Reduced redundancy: Eliminate repetitive code by encapsulating complex validation logic within reusable matchers.
Let's craft a custom password matcher with specific criteria like length, digits, and banned patterns.
fn be_a_valid_password<T: AsRef<str> + Debug>() -> Matchers<T> {
MatchersBuilder::start_building_with_inverted(be_empty().boxed())
.push(have_atleast_same_length(10).boxed())
.push(contain_a_digit().boxed())
.push(contain_any_of_characters(vec!['@', '#']).boxed())
.push_inverted(begin_with("pass").boxed())
.push_inverted(contain_ignoring_case("pass").boxed())
.push_inverted(contain_ignoring_case("word").boxed())
.combine_as_and()
}
The matcher enforces the following:
- Input must not be empty.
- Input must have a minimum length of 10 characters.
- Input must contain at least one digit.
- Input must contain any of the following characters: '@', '#'.
- Input must not begin with the string "pass" (case-insensitive).
- Input must not contain the strings "pass" or "word" (case-insensitive).
Let's combine it into a powerful assertion for valid passwords.
trait PasswordAssertion {
fn should_be_a_valid_password(&self) -> &Self;
}
impl PasswordAssertion for &str {
fn should_be_a_valid_password(&self) -> &Self {
self.should(&be_a_valid_password());
self
}
}
Time to put our password assertion to use.
#[test]
fn should_be_a_valid_password() {
let password = "P@@sw0rd9082";
password.should_be_a_valid_password();
}
Let's add one more condition for a password to be valid. It must be greater than the string "pass". That means, a password is valid if:
- it satisfies the previous matcher conditions
- it is greater than the string "pass"
It might appear that we can combine an OrderedMatcher
in the existing be_a_valid_password
method.
A key idea behind matcher compatibility is that the matchers can only be combined if they work with the same data type(s).
In this case, OrderedMatcher
handles any partially ordered types (T: PartialOrd)
, while other matchers in be_a_valid_password
work with string slices (&str).
To get around this, a separate matcher, be_greater_than_pass
, can be created to handle the "greater than 'pass'" condition. This matcher works with String values, ensuring compatibility.
fn be_greater_than_pass() -> Matchers<String> {
MatchersBuilder::start_building(be_greater_than(String::from("pass")).boxed())
.push_inverted(be_empty().boxed())
.combine_as_and()
}
It returns an instance of Matchers<String>
, so PasswordAssertion
is now implemented for String
. The method should_be_a_valid_password
now uses both the composed matchers to assert that the password is valid.
impl PasswordAssertion for String {
fn should_be_a_valid_password(&self) -> &Self {
self.should(&be_a_valid_password());
self.should(&be_greater_than_pass());
self
}
}
#[test]
fn should_be_a_valid_password() {
let password = "P@@sw0rd9082".to_string();
password.should_be_a_valid_password();
}
Rust features
clearcheck crate supports the following features:
- date enables assertions on date
- file enables assertions on filepath
- num enables assertions on float and assertions on integer
- regex enables regular expression assertions on string
Example project
Head over to the examples project to understand the usage of this crate.
Reference
Mention
- Google bard helped with the documentation and README :).
Dependencies
~0–8MB
~49K SLoC