Skip to content

Commit 6baa52f

Browse files
authored
Merge pull request #74 from innoave/feat/custom_assertion_without_expectation
Custom assertion without expectation
2 parents 17237a3 + 54da30d commit 6baa52f

3 files changed

Lines changed: 69 additions & 19 deletions

File tree

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@ Easy-to-extend means that we can write assertions for custom types with minimal
7070
1. use any predicate function as a custom assertion (see "[predicate as custom assertion]")
7171
2. property-based assertions can be used with any type that implements the related property
7272
(see "[property-based assertions]")
73-
3. write custom assertions by implementing two simple traits (see "[custom assertions]")
73+
3. write custom assertion methods by defining and implementing an extension trait
74+
(see "[custom assertions]")
7475

7576
The mentioned references link to a chapter in the crate's documentation that describes the
7677
possibilities for custom assertions, including examples.

src/lib.rs

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -415,12 +415,13 @@
415415
//!
416416
//! # Custom assertions
417417
//!
418-
//! `asserting` provides 4 ways to do custom assertions:
418+
//! `asserting` provides 5 ways to do custom assertions:
419419
//!
420420
//! 1. Predicate functions as custom assertions used with the [`Spec::satisfies()`] method
421421
//! 2. Property base assertions for any type that implements a property trait
422422
//! 3. Custom expectations used with the [`Spec::expecting()`] method
423423
//! 4. Custom assertions methods
424+
//! 5. Custom assertions without writing an expectation
424425
//!
425426
//! > 💡
426427
//! > Often the easiest way to assert a custom type is to write a helper
@@ -431,7 +432,15 @@
431432
//!
432433
//! How to use predicate functions as custom assertions is described on the
433434
//! [`Spec::satisfies()`] method and in the [Examples](#predicate-as-custom-assertion)
434-
//! chapter above. The other 3 ways are described in the following subchapters.
435+
//! chapter above. The other 4 ways are described in the following subchapters.
436+
//!
437+
//! [`Expectation`]s enable us to write specialized assertions by combining
438+
//! several basic expectations. In case a custom assertion cannot be composed
439+
//! out of the provided expectations but writing a custom [`Expectation`] is too
440+
//! cumbersome, we can write a custom assertion method directly without any
441+
//! custom [`Expectation`]. See the
442+
//! [Writing custom assertions without writing an expectation](#writing-custom-assertions-without-writing-an-expectation)
443+
//! chapter below for an example.
435444
//!
436445
//! ## Property-based assertions
437446
//!
@@ -687,6 +696,49 @@
687696
//! assert_that!(subject).is_left();
688697
//! ```
689698
//!
699+
//! ## Writing custom assertions without writing an expectation
700+
//!
701+
//! In real world projects custom assertions are often very specific, and custom
702+
//! expectations will not be reusable anyway. Writing a custom assertion without
703+
//! having to provide a custom [`Expectation`] is most likely the preferred way.
704+
//!
705+
//! Here is a simple assertion that checks whether a person is over 18 years
706+
//! old:
707+
//!
708+
//! ```
709+
//! use asserting::prelude::*;
710+
//! use asserting::spec::{FailingStrategy, Spec};
711+
//!
712+
//! struct Person {
713+
//! name: String,
714+
//! age: u8,
715+
//! }
716+
//!
717+
//! trait AssertOver18 {
718+
//! fn is_over_18(self) -> Self;
719+
//! }
720+
//!
721+
//! impl<'a, R> AssertOver18 for Spec<'a, Person, R>
722+
//! where
723+
//! R: FailingStrategy,
724+
//! {
725+
//! fn is_over_18(mut self) -> Self {
726+
//! let actual = self.subject().age;
727+
//! if actual < 18 {
728+
//! let expression = self.expression();
729+
//! self.do_fail_with_message(
730+
//! "expected {expression} to be over 18\n but was: {actual}\n expected: >= 18",
731+
//! );
732+
//! }
733+
//! self
734+
//! }
735+
//! }
736+
//!
737+
//! let person = Person { name: "Silvia".to_string(), age: 18 };
738+
//!
739+
//! assert_that!(person).is_over_18();
740+
//! ```
741+
//!
690742
//! [`AssertElements`]: assertions::AssertElements
691743
//! [`AssertFailure`]: spec::AssertFailure
692744
//! [`Expectation`]: spec::Expectation

src/spec/mod.rs

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -630,7 +630,7 @@ impl OwnedLocation {
630630
/// are collected in this struct.
631631
pub struct Spec<'a, S, R> {
632632
subject: S,
633-
expression: Option<Expression<'a>>,
633+
expression: Expression<'a>,
634634
description: Option<Cow<'a, str>>,
635635
location: Option<Location<'a>>,
636636
failures: Vec<AssertFailure>,
@@ -645,8 +645,8 @@ impl<S, R> Spec<'_, S, R> {
645645
}
646646

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

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

@@ -883,9 +883,8 @@ where
883883
#[track_caller]
884884
pub fn expecting(mut self, mut expectation: impl Expectation<S>) -> Self {
885885
if !expectation.test(&self.subject) {
886-
let default_expression = Expression::default();
887-
let expression = self.expression.as_ref().unwrap_or(&default_expression);
888-
let message = expectation.message(expression, &self.subject, false, &self.diff_format);
886+
let message =
887+
expectation.message(&self.expression, &self.subject, false, &self.diff_format);
889888
self.do_fail_with_message(message);
890889
}
891890
self
@@ -976,7 +975,7 @@ where
976975
/// Fails the assertion according the current failing strategy of this
977976
/// `Spec`.
978977
#[track_caller]
979-
fn do_fail_with_message(&mut self, message: impl Into<String>) {
978+
pub fn do_fail_with_message(&mut self, message: impl Into<String>) {
980979
let message = message.into();
981980
let failure = AssertFailure {
982981
description: self.description.clone().map(String::from),
@@ -1105,14 +1104,13 @@ impl<'a, I, R> Spec<'a, I, R> {
11051104
I: IntoIterator<Item = T>,
11061105
A: Fn(Spec<'a, T, CollectFailures>) -> Spec<'a, B, CollectFailures>,
11071106
{
1108-
let default_expression = &Expression::default();
1109-
let root_expression = self.expression.as_ref().unwrap_or(default_expression);
1107+
let root_expression = &self.expression;
11101108
let mut position = -1;
11111109
for item in self.subject {
11121110
position += 1;
11131111
let element_spec = Spec {
11141112
subject: item,
1115-
expression: Some(format!("{root_expression} [{position}]").into()),
1113+
expression: format!("{root_expression} [{position}]").into(),
11161114
description: None,
11171115
location: self.location,
11181116
failures: vec![],
@@ -1185,15 +1183,14 @@ impl<'a, I, R> Spec<'a, I, R> {
11851183
I: IntoIterator<Item = T>,
11861184
A: Fn(Spec<'a, T, CollectFailures>) -> Spec<'a, B, CollectFailures>,
11871185
{
1188-
let default_expression = &Expression::default();
1189-
let root_expression = self.expression.as_ref().unwrap_or(default_expression);
1186+
let root_expression = &self.expression;
11901187
let mut any_success = false;
11911188
let mut position = -1;
11921189
for item in self.subject {
11931190
position += 1;
11941191
let element_spec = Spec {
11951192
subject: item,
1196-
expression: Some(format!("{root_expression} [{position}]").into()),
1193+
expression: format!("{root_expression} [{position}]").into(),
11971194
description: None,
11981195
location: self.location,
11991196
failures: vec![],

0 commit comments

Comments
 (0)