Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ Easy-to-extend means that we can write assertions for custom types with minimal
1. use any predicate function as a custom assertion (see "[predicate as custom assertion]")
2. property-based assertions can be used with any type that implements the related property
(see "[property-based assertions]")
3. write custom assertions by implementing two simple traits (see "[custom assertions]")
3. write custom assertion methods by defining and implementing an extension trait
(see "[custom assertions]")

The mentioned references link to a chapter in the crate's documentation that describes the
possibilities for custom assertions, including examples.
Expand Down
56 changes: 54 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -415,12 +415,13 @@
//!
//! # Custom assertions
//!
//! `asserting` provides 4 ways to do custom assertions:
//! `asserting` provides 5 ways to do custom assertions:
//!
//! 1. Predicate functions as custom assertions used with the [`Spec::satisfies()`] method
//! 2. Property base assertions for any type that implements a property trait
//! 3. Custom expectations used with the [`Spec::expecting()`] method
//! 4. Custom assertions methods
//! 5. Custom assertions without writing an expectation
//!
//! > 💡
//! > Often the easiest way to assert a custom type is to write a helper
Expand All @@ -431,7 +432,15 @@
//!
//! How to use predicate functions as custom assertions is described on the
//! [`Spec::satisfies()`] method and in the [Examples](#predicate-as-custom-assertion)
//! chapter above. The other 3 ways are described in the following subchapters.
//! chapter above. The other 4 ways are described in the following subchapters.
//!
//! [`Expectation`]s enable us to write specialized assertions by combining
//! several basic expectations. In case a custom assertion cannot be composed
//! out of the provided expectations but writing a custom [`Expectation`] is too
//! cumbersome, we can write a custom assertion method directly without any
//! custom [`Expectation`]. See the
//! [Writing custom assertions without writing an expectation](#writing-custom-assertions-without-writing-an-expectation)
//! chapter below for an example.
//!
//! ## Property-based assertions
//!
Expand Down Expand Up @@ -687,6 +696,49 @@
//! assert_that!(subject).is_left();
//! ```
//!
//! ## Writing custom assertions without writing an expectation
//!
//! In real world projects custom assertions are often very specific, and custom
//! expectations will not be reusable anyway. Writing a custom assertion without
//! having to provide a custom [`Expectation`] is most likely the preferred way.
//!
//! Here is a simple assertion that checks whether a person is over 18 years
//! old:
//!
//! ```
//! use asserting::prelude::*;
//! use asserting::spec::{FailingStrategy, Spec};
//!
//! struct Person {
//! name: String,
//! age: u8,
//! }
//!
//! trait AssertOver18 {
//! fn is_over_18(self) -> Self;
//! }
//!
//! impl<'a, R> AssertOver18 for Spec<'a, Person, R>
//! where
//! R: FailingStrategy,
//! {
//! fn is_over_18(mut self) -> Self {
//! let actual = self.subject().age;
//! if actual < 18 {
//! let expression = self.expression();
//! self.do_fail_with_message(
//! "expected {expression} to be over 18\n but was: {actual}\n expected: >= 18",
//! );
//! }
//! self
//! }
//! }
//!
//! let person = Person { name: "Silvia".to_string(), age: 18 };
//!
//! assert_that!(person).is_over_18();
//! ```
//!
//! [`AssertElements`]: assertions::AssertElements
//! [`AssertFailure`]: spec::AssertFailure
//! [`Expectation`]: spec::Expectation
Expand Down
29 changes: 13 additions & 16 deletions src/spec/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -630,7 +630,7 @@ impl OwnedLocation {
/// are collected in this struct.
pub struct Spec<'a, S, R> {
subject: S,
expression: Option<Expression<'a>>,
expression: Expression<'a>,
description: Option<Cow<'a, str>>,
location: Option<Location<'a>>,
failures: Vec<AssertFailure>,
Expand All @@ -645,8 +645,8 @@ impl<S, R> Spec<'_, S, R> {
}

/// Returns the expression (or subject name) if one has been set.
pub fn expression(&self) -> Option<&Expression<'_>> {
self.expression.as_ref()
pub fn expression(&self) -> &Expression<'_> {
&self.expression
}

/// Returns the location in source code or test code if it has been set.
Expand Down Expand Up @@ -692,10 +692,10 @@ impl<'a, S, R> Spec<'a, S, R> {
/// The diff format is set to "no highlighting". Failure messages will not
/// highlight differences between the actual and the expected value.
#[must_use = "a spec does nothing unless an assertion method is called"]
pub const fn new(subject: S, failing_strategy: R) -> Self {
pub fn new(subject: S, failing_strategy: R) -> Self {
Self {
subject,
expression: None,
expression: Expression::default(),
description: None,
location: None,
failures: vec![],
Expand All @@ -707,7 +707,7 @@ impl<'a, S, R> Spec<'a, S, R> {
/// Sets the subject name or expression for this assertion.
#[must_use = "a spec does nothing unless an assertion method is called"]
pub fn named(mut self, subject_name: impl Into<Cow<'a, str>>) -> Self {
self.expression = Some(Expression(subject_name.into()));
self.expression = Expression(subject_name.into());
self
}

Expand Down Expand Up @@ -883,9 +883,8 @@ where
#[track_caller]
pub fn expecting(mut self, mut expectation: impl Expectation<S>) -> Self {
if !expectation.test(&self.subject) {
let default_expression = Expression::default();
let expression = self.expression.as_ref().unwrap_or(&default_expression);
let message = expectation.message(expression, &self.subject, false, &self.diff_format);
let message =
expectation.message(&self.expression, &self.subject, false, &self.diff_format);
self.do_fail_with_message(message);
}
self
Expand Down Expand Up @@ -976,7 +975,7 @@ where
/// Fails the assertion according the current failing strategy of this
/// `Spec`.
#[track_caller]
fn do_fail_with_message(&mut self, message: impl Into<String>) {
pub fn do_fail_with_message(&mut self, message: impl Into<String>) {
let message = message.into();
let failure = AssertFailure {
description: self.description.clone().map(String::from),
Expand Down Expand Up @@ -1105,14 +1104,13 @@ impl<'a, I, R> Spec<'a, I, R> {
I: IntoIterator<Item = T>,
A: Fn(Spec<'a, T, CollectFailures>) -> Spec<'a, B, CollectFailures>,
{
let default_expression = &Expression::default();
let root_expression = self.expression.as_ref().unwrap_or(default_expression);
let root_expression = &self.expression;
let mut position = -1;
for item in self.subject {
position += 1;
let element_spec = Spec {
subject: item,
expression: Some(format!("{root_expression} [{position}]").into()),
expression: format!("{root_expression} [{position}]").into(),
description: None,
location: self.location,
failures: vec![],
Expand Down Expand Up @@ -1185,15 +1183,14 @@ impl<'a, I, R> Spec<'a, I, R> {
I: IntoIterator<Item = T>,
A: Fn(Spec<'a, T, CollectFailures>) -> Spec<'a, B, CollectFailures>,
{
let default_expression = &Expression::default();
let root_expression = self.expression.as_ref().unwrap_or(default_expression);
let root_expression = &self.expression;
let mut any_success = false;
let mut position = -1;
for item in self.subject {
position += 1;
let element_spec = Spec {
subject: item,
expression: Some(format!("{root_expression} [{position}]").into()),
expression: format!("{root_expression} [{position}]").into(),
description: None,
location: self.location,
failures: vec![],
Expand Down
Loading