diff --git a/src/assertions.rs b/src/assertions.rs index c473ddd..aeca39a 100644 --- a/src/assertions.rs +++ b/src/assertions.rs @@ -256,6 +256,230 @@ pub trait AssertEquivalence { fn is_not_equivalent_to(self, expected: E) -> Self; } +/// Assert whether a single value is in a set of expected values. +/// +/// # Examples +/// +/// ``` +/// use asserting::prelude::*; +/// +/// assert_that!("two").is_in(["one", "two", "three"]); +/// assert_that!(42).is_in([22, 42, 50, 88]); +/// +/// assert_that!("zero").is_not_in(["one", "two", "three"]); +/// assert_that!(42).is_not_in([1, 2, 3, 4, 5, 6]); +/// ``` +/// +/// The assertions [`is_in`] and [`is_not_in`] fail if the expected set of values +/// is empty. This restrictive behavior is by design. In the vast majority of +/// tests asserting against an empty set results in a "dead test". Asserting +/// that a value is in an empty set always fails, while asserting that a value +/// is not in an empty set always succeeds. So the assertion result does not +/// depend on the actual value. This might not be what we want to assert. +/// +/// ``` +/// # #[cfg(feature = "panic")] +/// # { +/// use asserting::prelude::*; +/// +/// assert_that_code!(|| { +/// assert_that!(42).is_in([]); // always fails +/// }).panics_with_message( +/// r"expected 42 to be in [] +/// An assertion using `is_in()` was made against an empty collection. +/// This would always fail, which is likely a bug in your test setup. +/// If an empty collection should be allowed in your test, use `is_in_maybe_empty()`. +/// "); +/// +/// assert_that_code!(|| { +/// assert_that!(42).is_not_in([]); // always fails +/// }).panics_with_message( +/// r"expected 42 to not be in [] +/// An assertion using `is_not_in()` was made against an empty collection. +/// This would always succeed, which is likely a bug in your test setup. +/// If an empty collection should be allowed in your test, use `is_not_in_maybe_empty()`. +/// "); +/// # } +/// ``` +/// +/// If you need the mathematical correct behavior for some specific test, use +/// the alternative methods [`is_in_maybe_empty`] and [`is_not_in_maybe_empty`]. +/// +/// ``` +/// use asserting::prelude::*; +/// +/// # #[cfg(feature = "panic")] +/// # { +/// assert_that_code!(|| { +/// assert_that!(42) +/// .with_diff_format(DIFF_FORMAT_NO_HIGHLIGHT) +/// .is_in_maybe_empty([]); // always fails +/// }).panics_with_message( +/// r"expected 42 to be in [] +/// but was: 42 +/// which is not in: [] +/// "); +/// # } +/// +/// assert_that!(42).is_not_in_maybe_empty([]); // always succeeds +/// ``` +/// +/// [`is_in`]: Self::is_in +/// [`is_in_maybe_empty`]: Self::is_in_maybe_empty +/// [`is_not_in`]: Self::is_not_in +/// [`is_not_in_maybe_empty`]: Self::is_not_in_maybe_empty +pub trait AssertIsIn { + /// Verifies that the actual value is in the set of expected values. + /// + /// The set of expected values can be given in any type of collection + /// that implements the `IntoIterator` trait. + /// + /// If the expected set of values is empty, this assertion always fails + /// regardless of the actual value. This is a so-called "dead test" as it + /// does not assert anything about the actual value. This might be a bug + /// or design flaw of the test case. The failure message of the failing + /// assertion gives a hint that this test might not be what you want. + /// + /// If you explicitly want to allow the empty set for some special test, + /// use the alternative [`is_in_maybe_empty`] method. + /// + /// # Examples + /// + /// ``` + /// use asserting::prelude::*; + /// + /// assert_that!("two").is_in(["one", "two", "three"]); + /// assert_that!(42).is_in([1, 2, 42, 5, 6]); + /// + /// # #[cfg(feature = "panic")] + /// # { + /// assert_that_code!(|| { + /// assert_that!(42).is_in([]); // always fails + /// }).panics_with_message( + /// r"expected 42 to be in [] + /// An assertion using `is_in()` was made against an empty collection. + /// This would always fail, which is likely a bug in your test setup. + /// If an empty collection should be allowed in your test, use `is_in_maybe_empty()`. + /// "); + /// # } + /// ``` + /// + /// [`is_in_maybe_empty`]: Self::is_in_maybe_empty + #[track_caller] + fn is_in(self, expected_values: I) -> Self; + + /// Verifies that the actual value is in the set of expected values + /// with the empty set allowed. + /// + /// The set of expected values can be given in any type of collection + /// that implements the `IntoIterator` trait. + /// + /// This assertion is basically the same as the [`is_in`] method, but it + /// explicitly allows the empty set for the expected values. Asserting + /// against an empty set is most likely a "dead test", as the test always + /// fails independent of the actual value. So when using this method, make + /// sure your test is actually testing something. + /// + /// If there is no good reason for using this method, stick with the more + /// restrictive [`is_in`] method. + /// + /// # Examples + /// + /// ``` + /// use asserting::prelude::*; + /// + /// assert_that!("two").is_in_maybe_empty(["one", "two", "three"]); + /// assert_that!(42).is_in_maybe_empty([1, 2, 42, 5, 6]); + /// + /// # #[cfg(feature = "panic")] + /// # { + /// assert_that_code!(|| { + /// assert_that!(42) + /// .with_diff_format(DIFF_FORMAT_NO_HIGHLIGHT) + /// .is_in_maybe_empty([]); // always fails + /// }).panics_with_message( + /// r"expected 42 to be in [] + /// but was: 42 + /// which is not in: [] + /// "); + /// # } + /// ``` + /// + /// [`is_in`]: Self::is_in + #[track_caller] + fn is_in_maybe_empty(self, expected_values: I) -> Self; + + /// Verifies that the actual value is not in the set of expected values. + /// + /// The set of expected values can be given in any type of collection + /// that implements the `IntoIterator` trait. + /// + /// This assertion fails if the expected set of values is empty. This + /// behavior is different from the mathematically correct way. This is by + /// design as in the mathematical sense any element is not in the empty set. + /// But in the vast majority of tests this would be a "dead test" as the + /// assertion always succeeds independent of the actual value. In fact, the + /// test does not assert anything about the actual value. + /// + /// If for some test you need the mathematically correct interpretation, use + /// the alternative [`is_not_in_maybe_empty`] method. + /// + /// # Examples + /// + /// ``` + /// use asserting::prelude::*; + /// + /// assert_that!("zero").is_not_in(["one", "two", "three"]); + /// assert_that!(42).is_not_in([1, 2, 3, 4, 5, 6]); + /// + /// # #[cfg(feature = "panic")] + /// # { + /// assert_that_code!(|| { + /// assert_that!(42).is_not_in([]); // always fails + /// }).panics_with_message( + /// r"expected 42 to not be in [] + /// An assertion using `is_not_in()` was made against an empty collection. + /// This would always succeed, which is likely a bug in your test setup. + /// If an empty collection should be allowed in your test, use `is_not_in_maybe_empty()`. + /// "); + /// # } + /// ``` + /// + /// [`is_not_in_maybe_empty`]: Self::is_not_in_maybe_empty + #[track_caller] + fn is_not_in(self, expected_values: I) -> Self; + + /// Verifies that the actual value is not in the set of expected values + /// with the empty set allowed. + /// + /// The set of expected values can be given in any type of collection + /// that implements the `IntoIterator` trait. + /// + /// This assertion is basically the same as the [`is_not_in`] method, but it + /// explicitly allows the empty set for the expected values. Asserting + /// against an empty set is most likely a "dead test", as the test always + /// succeeds independent of the actual value. So when using this method, + /// make sure your test is actually testing something. + /// + /// If there is no good reason for using this method, stick with the more + /// restrictive [`is_not_in`] method. + /// + /// # Examples + /// + /// ``` + /// use asserting::prelude::*; + /// + /// assert_that!("zero").is_not_in_maybe_empty(["one", "two", "three"]); + /// assert_that!(42).is_not_in_maybe_empty([1, 2, 3, 4, 5, 6]); + /// + /// assert_that!(42).is_not_in_maybe_empty([]); // always succeeds + /// ``` + /// + /// [`is_not_in`]: Self::is_not_in + #[track_caller] + fn is_not_in_maybe_empty(self, expected_values: I) -> Self; +} + /// Assert approximate equality for floating point numbers. /// /// # Examples @@ -275,7 +499,7 @@ pub trait AssertIsCloseToWithinMargin { /// Verifies that the actual value is approximately equal to the expected /// value. /// - /// For comparison, the epsilon and ULPS values of the given margin are + /// For the comparison the epsilon and ULPS values of the given margin are /// used. /// /// # Examples @@ -298,7 +522,7 @@ pub trait AssertIsCloseToWithinMargin { /// Verifies that the actual value not approximately equals to the expected /// value. /// - /// For comparison, the epsilon and ULPS values of the given margin are + /// For the comparison the epsilon and ULPS values of the given margin are /// used. /// /// # Examples diff --git a/src/derived_spec/mod.rs b/src/derived_spec/mod.rs index bec710f..5fdcd06 100644 --- a/src/derived_spec/mod.rs +++ b/src/derived_spec/mod.rs @@ -5,11 +5,12 @@ use crate::assertions::{ AssertBoolean, AssertChar, AssertDebugString, AssertDecimalNumber, AssertDisplayString, AssertElements, AssertEmptiness, AssertEquality, AssertErrorHasSource, AssertHasCharCount, AssertHasDebugString, AssertHasDisplayString, AssertHasError, AssertHasErrorMessage, - AssertHasLength, AssertHasValue, AssertInRange, AssertInfinity, AssertIteratorContains, - AssertIteratorContainsInAnyOrder, AssertIteratorContainsInOrder, AssertMapContainsKey, - AssertMapContainsValue, AssertNotANumber, AssertNumericIdentity, AssertOption, - AssertOptionValue, AssertOrder, AssertOrderedElements, AssertOrderedElementsRef, AssertResult, - AssertResultValue, AssertSameAs, AssertSignum, AssertStringContainsAnyOf, AssertStringPattern, + AssertHasLength, AssertHasValue, AssertInRange, AssertInfinity, AssertIsIn, + AssertIteratorContains, AssertIteratorContainsInAnyOrder, AssertIteratorContainsInOrder, + AssertMapContainsKey, AssertMapContainsValue, AssertNotANumber, AssertNumericIdentity, + AssertOption, AssertOptionValue, AssertOrder, AssertOrderedElements, AssertOrderedElementsRef, + AssertResult, AssertResultValue, AssertSameAs, AssertSignum, AssertStringContainsAnyOf, + AssertStringPattern, }; use crate::expectations::{ error_has_source, error_has_source_message, has_at_least_char_count, has_at_least_length, @@ -19,9 +20,9 @@ use crate::expectations::{ has_length_in_range, has_length_less_than, has_precision_of, has_scale_of, has_value, is_a_number, is_after, is_alphabetic, is_alphanumeric, is_ascii, is_at_least, is_at_most, is_before, is_between, is_control_char, is_digit, is_empty, is_equal_to, is_err, is_false, - is_finite, is_greater_than, is_in_range, is_infinite, is_integer, is_less_than, is_lower_case, - is_negative, is_none, is_ok, is_one, is_positive, is_same_as, is_some, is_true, is_upper_case, - is_whitespace, is_zero, iterator_contains, iterator_contains_all_in_order, + is_finite, is_greater_than, is_in, is_in_range, is_infinite, is_integer, is_less_than, + is_lower_case, is_negative, is_none, is_ok, is_one, is_positive, is_same_as, is_some, is_true, + is_upper_case, is_whitespace, is_zero, iterator_contains, iterator_contains_all_in_order, iterator_contains_all_of, iterator_contains_any_of, iterator_contains_exactly, iterator_contains_exactly_in_any_order, iterator_contains_only, iterator_contains_only_once, iterator_contains_sequence, iterator_ends_with, iterator_starts_with, @@ -645,6 +646,30 @@ where } } +impl AssertIsIn for DerivedSpec<'_, O, S, D> +where + I: IntoIterator, + S: PartialEq, + D: Represent + Represent, + O: DoFail, +{ + fn is_in(self, expected_values: I) -> Self { + self.expecting(is_in(expected_values)) + } + + fn is_in_maybe_empty(self, expected_values: I) -> Self { + self.expecting(is_in(expected_values).allow_empty()) + } + + fn is_not_in(self, expected_values: I) -> Self { + self.expecting(not(is_in(expected_values))) + } + + fn is_not_in_maybe_empty(self, expected_values: I) -> Self { + self.expecting(not(is_in(expected_values).allow_empty())) + } +} + #[cfg(feature = "float-cmp")] mod float_cmp { use super::DerivedSpec; diff --git a/src/derived_spec/tests.rs b/src/derived_spec/tests.rs index 1f9bfa3..1ab8399 100644 --- a/src/derived_spec/tests.rs +++ b/src/derived_spec/tests.rs @@ -269,6 +269,17 @@ fn extracting_ref_string_is_same_as() { .is_same_as("Alexander".to_string()); } +#[test] +fn extracting_ref_string_is_in_array_of_str() { + struct Name(String); + + let name = Name("Alex".to_string()); + + assert_that(name) + .extracting_ref("0", |n| &n.0) + .is_in(["Susan", "Gabi", "Alex"]); +} + #[test] fn extracting_ref_i32_is_zero() { struct Int(i32); @@ -332,6 +343,17 @@ fn extracting_ref_i32_is_in_range() { .is_in_range(1..=9); } +#[test] +fn extracting_ref_i32_is_in_array_of_i32() { + struct Int(i32); + + let number = Int(42); + + assert_that(number) + .extracting_ref("0", |n| &n.0) + .is_in([0, 1, 2, 3, 42, 5, 6, 7, 8]); +} + #[cfg(feature = "float-cmp")] #[test] fn extracting_ref_f32_is_close_to() { diff --git a/src/equality.rs b/src/equality.rs index e30093a..dc9ca53 100644 --- a/src/equality.rs +++ b/src/equality.rs @@ -1,13 +1,16 @@ //! Implementation of the equality assertions. use crate::assertions::{ - AssertEquality, AssertHasDebugString, AssertHasDisplayString, AssertSameAs, + AssertEquality, AssertHasDebugString, AssertHasDisplayString, AssertIsIn, AssertSameAs, +}; +use crate::colored::{ + mark_all_items_in_collection, mark_diff, mark_diff_str, mark_missing, mark_unexpected, }; -use crate::colored::{mark_diff, mark_diff_str}; use crate::expectations::{ - HasDebugString, HasDisplayString, IsEqualTo, IsSameAs, has_debug_string, has_display_string, - is_equal_to, is_same_as, not, + HasDebugString, HasDisplayString, IsEqualTo, IsIn, IsSameAs, has_debug_string, + has_display_string, is_equal_to, is_in, is_same_as, not, }; +use crate::failure_empty_collection; use crate::spec::{ DiffFormat, Expectation, Expecting, Expression, FailingStrategy, Invertible, Represent, Represented, Spec, @@ -16,6 +19,7 @@ use crate::std::{ fmt::{Debug, Display}, format, string::{String, ToString}, + vec::Vec, }; impl AssertEquality for Spec<'_, S, D, R> @@ -192,3 +196,87 @@ where } impl Invertible for HasDisplayString {} + +impl AssertIsIn for Spec<'_, S, D, R> +where + I: IntoIterator, + S: PartialEq, + D: Represent + Represent, + R: FailingStrategy, +{ + fn is_in(self, expected_values: I) -> Self { + self.expecting(is_in(expected_values)) + } + + fn is_in_maybe_empty(self, expected_values: I) -> Self { + self.expecting(is_in(expected_values).allow_empty()) + } + + fn is_not_in(self, expected_values: I) -> Self { + self.expecting(not(is_in(expected_values))) + } + + fn is_not_in_maybe_empty(self, expected_values: I) -> Self { + self.expecting(not(is_in(expected_values).allow_empty())) + } +} + +impl Expectation for IsIn +where + S: PartialEq, + D: Represent + Represent, +{ + fn test(&mut self, subject: &S) -> bool { + if !self.allow_empty && self.expected_values.is_empty() { + return self.inverted; + } + self.expected_values + .iter() + .any(|expected| subject == expected) + } + + fn message( + &self, + expression: &Expression<'_>, + actual: &S, + inverted: bool, + representation: &D, + format: &DiffFormat, + ) -> String { + let not = if inverted { "not " } else { "" }; + let expected_values = self + .expected_values + .iter() + .map(|expected| Represented::from((expected, representation))) + .collect::>(); + if !self.allow_empty && self.expected_values.is_empty() { + let (method, behavior, alternative) = if inverted { + ("is_not_in", "succeed", "is_not_in_maybe_empty") + } else { + ("is_in", "fail", "is_in_maybe_empty") + }; + return format!( + "expected {expression} to {not}be in {expected_values:?}\n{}", + failure_empty_collection(method, behavior, alternative) + ); + } + let marked_actual = mark_unexpected(actual, representation, format); + let marked_expected = mark_all_items_in_collection( + &self.expected_values, + representation, + format, + mark_missing, + ); + format!( + r"expected {expression} to {not}be in {expected_values:?} + but was: {marked_actual} + which is not in: {marked_expected}", + ) + } +} + +impl Invertible for IsIn { + fn set_inverted(&mut self) { + self.inverted = !self.inverted; + } +} diff --git a/src/expectations.rs b/src/expectations.rs index c0dc191..89302ce 100644 --- a/src/expectations.rs +++ b/src/expectations.rs @@ -3,6 +3,7 @@ #![allow(missing_docs)] #![warn(clippy::return_self_not_must_use)] +use crate::spec::Invertible; use crate::std::{marker::PhantomData, string::String, vec::Vec}; use hashbrown::HashSet; #[cfg(feature = "regex")] @@ -22,7 +23,11 @@ use regex::Regex; /// assert_that!([1, 2, 3]).expecting(not(HasLength { expected_length: 4 })); /// assert_that!("almost").expecting(not(StringContains { expected: "entire" })); /// ``` -pub fn not(expectation: E) -> Not { +pub fn not(mut expectation: E) -> Not +where + E: Invertible, +{ + expectation.set_inverted(); Not(expectation) } @@ -258,6 +263,33 @@ pub struct IsSameAs { pub expected: E, } +pub fn is_in(expected_values: I) -> IsIn +where + I: IntoIterator, +{ + IsIn { + allow_empty: false, + inverted: false, + expected_values: Vec::from_iter(expected_values), + } +} + +#[must_use] +pub struct IsIn { + pub allow_empty: bool, + pub inverted: bool, + pub expected_values: Vec, +} + +impl IsIn { + pub fn allow_empty(self) -> Self { + Self { + allow_empty: true, + ..self + } + } +} + /// Creates an [`IsCloseTo`] expectation. /// /// The margin is set to a default value. To define a custom margin, use the diff --git a/src/integer/tests.rs b/src/integer/tests.rs index e4df857..0375fc6 100644 --- a/src/integer/tests.rs +++ b/src/integer/tests.rs @@ -1,4 +1,6 @@ use crate::prelude::*; +use crate::std::vec; +use hashbrown::HashSet; #[test] fn usize_is_equal_to_usize() { @@ -478,6 +480,125 @@ fn verify_u64_is_one_fails() { ); } +#[test] +fn i32_is_in_array_of_i32() { + let subject: i32 = 32; + + assert_that(subject).is_in([20, 42, 32, 100]); +} + +#[test] +fn i32_is_in_vec_of_i32() { + let subject: i32 = 32; + + assert_that(subject).is_in(vec![32, 42, 20, 100]); +} + +#[test] +fn i32_is_in_hashset_of_i32() { + let subject: i32 = 42; + + let expected_values: HashSet = HashSet::from([20, 42, 32, 100]); + + assert_that(subject).is_in(expected_values); +} + +#[test] +fn borrowed_i32_is_in_borrowed_array_of_i32() { + let subject: &i32 = &32; + + assert_that(subject).is_in(&[32, 42, 20, 100]); +} + +#[test] +fn borrowed_i32_is_in_slice_of_i32() { + let subject: &i32 = &32; + + assert_that(subject).is_in(&[32, 42, 20, 100][..]); +} + +#[test] +fn verify_i32_is_in_empty_array_fails() { + let subject: i32 = 42; + + let failures = verify_that!(subject).is_in([]).display_failures(); + + assert_that!(failures).single_element().is_equal_to( + r"expected subject to be in [] + An assertion using `is_in()` was made against an empty collection. + This would always fail, which is likely a bug in your test setup. + If an empty collection should be allowed in your test, use `is_in_maybe_empty()`. +", + ); +} + +#[test] +fn verify_i32_is_in_maybe_empty_for_empty_array_fails() { + let subject: i32 = 42; + + let failures = verify_that!(subject) + .is_in_maybe_empty([]) + .display_failures(); + + assert_that!(failures).single_element().is_equal_to( + r"expected subject to be in [] + but was: 42 + which is not in: [] +", + ); +} + +#[test] +fn verify_i32_is_in_array_with_several_i32_fails() { + let subject: i32 = 42; + + let failures = verify_that!(subject) + .is_in([43, 11, 22, 33, 88]) + .display_failures(); + + assert_that!(failures).single_element().is_equal_to( + r"expected subject to be in [43, 11, 22, 33, 88] + but was: 42 + which is not in: [43, 11, 22, 33, 88] +", + ); +} + +#[test] +fn i32_is_not_in_array_of_i32() { + let subject: i32 = 41; + + assert_that(subject).is_not_in([20, 42, 32, 100]); +} + +#[test] +fn verify_i32_is_not_in_empty_array_fails() { + let subject: i32 = 41; + + let expected_values: [i32; 0] = []; + + let failures = verify_that(subject) + .is_not_in(expected_values) + .display_failures(); + + assert_that!(failures).single_element().is_equal_to( + r"expected subject to not be in [] + An assertion using `is_not_in()` was made against an empty collection. + This would always succeed, which is likely a bug in your test setup. + If an empty collection should be allowed in your test, use `is_not_in_maybe_empty()`. +", + ); +} + +#[test] +fn is_not_in_maybe_empty_for_empty_array() { + let subject: i32 = 42; + + let expected_values: [i32; 0] = []; + + assert_that(subject).is_not_in_maybe_empty(expected_values); +} + #[cfg(feature = "colored")] mod colored { use crate::prelude::*; diff --git a/src/lib.rs b/src/lib.rs index e0a0548..6b6a19e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1158,6 +1158,16 @@ mod slice; mod string; mod vec; +use crate::std::{format, string::String}; + +fn failure_empty_collection(method: &str, behavior: &str, alternative: &str) -> String { + format!( + " An assertion using `{method}()` was made against an empty collection.\n \ + This would always {behavior}, which is likely a bug in your test setup.\n \ + If an empty collection should be allowed in your test, use `{alternative}()`." + ) +} + // test code snippets in the README.md #[cfg(doctest)] #[doc = include_str!("../README.md")] diff --git a/src/spec/mod.rs b/src/spec/mod.rs index 39a814f..477b69d 100644 --- a/src/spec/mod.rs +++ b/src/spec/mod.rs @@ -461,7 +461,18 @@ pub trait Expectation { /// clearly states whether the expectation has been inverted or not. /// /// [`Not`]: crate::expectations::Not -pub trait Invertible {} +pub trait Invertible { + /// This method is called, when an [`Expectation`] is inverted by the + /// [`not`] factory method. + /// + /// It provides a default implementation which does noting. + /// + /// Expectations may implement it to store the "inverted" flag in their + /// struct for being used in the [`Expectation::test`] method. + /// + /// [`not`]: crate::expectations::not + fn set_inverted(&mut self) {} +} /// A textual representation of the expression or subject that is being /// asserted. diff --git a/src/string/tests.rs b/src/string/tests.rs index b86af21..fd01711 100644 --- a/src/string/tests.rs +++ b/src/string/tests.rs @@ -1,5 +1,7 @@ use crate::prelude::*; use crate::std::string::{String, ToString}; +use crate::std::vec; +use hashbrown::HashSet; #[test] fn string_is_equal_to_string() { @@ -1373,6 +1375,207 @@ fn verify_string_does_not_end_with_char_fails() { ); } +#[test] +fn string_is_in_an_array_of_strings() { + let subject: String = "tempor nihil in minim".to_string(); + + assert_that(subject).is_in([ + "aliquip nulla eros odio".to_string(), + "tempor nihil in minim".to_string(), + "id culpa et in".to_string(), + ]); +} + +#[test] +fn string_is_in_a_vec_of_strings() { + let subject: String = "tempor nihil in minim".to_string(); + + assert_that(subject).is_in(vec![ + "aliquip nulla eros odio".to_string(), + "tempor nihil in minim".to_string(), + "id culpa et in".to_string(), + ]); +} + +#[test] +fn string_is_in_a_array_of_str() { + let subject: String = "tempor nihil in minim".to_string(); + + assert_that(subject).is_in([ + "aliquip nulla eros odio", + "tempor nihil in minim", + "id culpa et in", + ]); +} + +#[test] +fn str_is_in_an_array_of_strings() { + let subject: &str = "tempor nihil in minim"; + + assert_that(subject).is_in([ + "aliquip nulla eros odio".to_string(), + "tempor nihil in minim".to_string(), + "id culpa et in".to_string(), + ]); +} + +#[test] +fn str_is_in_a_vec_of_strings() { + let subject: &str = "tempor nihil in minim"; + + assert_that(subject).is_in(vec![ + "aliquip nulla eros odio".to_string(), + "tempor nihil in minim".to_string(), + "id culpa et in".to_string(), + ]); +} + +#[test] +fn str_is_in_a_array_of_str() { + let subject: &str = "tempor nihil in minim"; + + assert_that(subject).is_in([ + "aliquip nulla eros odio", + "tempor nihil in minim", + "id culpa et in", + ]); +} + +#[test] +fn str_is_in_borrowed_array_of_string() { + let subject: &str = "tempor nihil in minim"; + + assert_that(subject).is_in(&[ + "aliquip nulla eros odio".to_string(), + "tempor nihil in minim".to_string(), + "id culpa et in".to_string(), + ]); +} + +#[test] +fn str_is_in_slice_of_string() { + let subject: &str = "tempor nihil in minim"; + + assert_that(subject).is_in( + &[ + "aliquip nulla eros odio".to_string(), + "tempor nihil in minim".to_string(), + "id culpa et in".to_string(), + ][..], + ); +} + +#[test] +fn str_is_in_hashset_of_str() { + let subject: &str = "zero"; + + let expected_values: HashSet<&str> = HashSet::from_iter(["one", "two", "three", "zero"]); + + assert_that(subject).is_in(expected_values); +} + +#[test] +fn verify_str_is_in_empty_array_fails() { + let subject: &str = "tempor nihil in minim"; + + let expected_values: [&str; 0] = []; + + let failures = verify_that!(subject) + .is_in(expected_values) + .display_failures(); + + assert_that!(failures).single_element().is_equal_to( + r"expected subject to be in [] + An assertion using `is_in()` was made against an empty collection. + This would always fail, which is likely a bug in your test setup. + If an empty collection should be allowed in your test, use `is_in_maybe_empty()`. +", + ); +} + +#[test] +fn verify_string_is_in_maybe_empty_for_empty_array_fails() { + let subject: &str = "tempor nihil in minim"; + + let expected_values: [&str; 0] = []; + + let failures = verify_that!(subject) + .is_in_maybe_empty(expected_values) + .display_failures(); + + assert_that!(failures).single_element().is_equal_to( + r#"expected subject to be in [] + but was: "tempor nihil in minim" + which is not in: [] +"#, + ); +} + +#[test] +fn verify_str_is_in_array_of_several_str_fails() { + let subject: &str = "zero"; + + let failures = verify_that!(subject) + .is_in(["one", "two", "three", "four"]) + .display_failures(); + + assert_that!(failures).single_element().is_equal_to( + r#"expected subject to be in ["one", "two", "three", "four"] + but was: "zero" + which is not in: ["one", "two", "three", "four"] +"#, + ); +} + +#[test] +fn string_is_not_in_array_of_strings() { + let subject: String = "tempor nihil in minim".to_string(); + + assert_that(subject).is_not_in([ + "aliquip nulla eros odio".to_string(), + "id culpa et in".to_string(), + ]); +} + +#[test] +fn str_is_not_in_array_of_str() { + let subject: &str = "tempor nihil in minim"; + + assert_that(subject).is_not_in([ + "aliquip nulla eros odio", + "voluptate no lobortis qui", + "id culpa et in", + ]); +} + +#[test] +fn verify_string_is_not_in_an_empty_array_fails() { + let subject: String = "tempor nihil in minim".to_string(); + + let expected_values: [String; 0] = []; + + let failures = verify_that(subject) + .is_not_in(expected_values) + .display_failures(); + + assert_that!(failures).single_element().is_equal_to( + r"expected subject to not be in [] + An assertion using `is_not_in()` was made against an empty collection. + This would always succeed, which is likely a bug in your test setup. + If an empty collection should be allowed in your test, use `is_not_in_maybe_empty()`. +", + ); +} + +#[test] +fn string_is_not_in_maybe_empty_for_empty_array() { + let subject: String = "tempor nihil in minim".to_string(); + + let expected_values: [String; 0] = []; + + assert_that(subject).is_not_in_maybe_empty(expected_values); +} + #[cfg(feature = "regex")] mod regex { use crate::prelude::*; diff --git a/tests/assert_custom_types.rs b/tests/assert_custom_types.rs new file mode 100644 index 0000000..bdadea6 --- /dev/null +++ b/tests/assert_custom_types.rs @@ -0,0 +1,73 @@ +//! This module tests the assertion methods on various custom types. +#![allow(unused_crate_dependencies)] + +use asserting::prelude::*; + +#[derive(Debug, PartialEq)] +struct IntVal(i32); + +#[test] +fn int_val_is_in_array_of_single_int_val() { + let subject = IntVal(42); + + assert_that!(subject).is_in([IntVal(42)]); +} + +#[test] +fn int_val_is_in_array_of_several_int_val() { + let subject = IntVal(42); + + assert_that!(subject).is_in([IntVal(33), IntVal(42), IntVal(99)]); +} + +#[test] +fn int_val_ref_is_in_borrowed_array_of_int_val() { + let subject = IntVal(42); + + assert_that!(&subject).is_in(&[IntVal(42), IntVal(11), IntVal(28)]); +} + +#[test] +fn int_val_ref_is_in_slice_of_several_int_val() { + let subject = IntVal(42); + + assert_that!(&subject).is_in(&[IntVal(90), IntVal(42)][..]); +} + +#[test] +fn int_val_ref_is_in_maybe_empty_slice_of_several_int_val() { + let subject = IntVal(42); + + assert_that!(&subject).is_in_maybe_empty(&[IntVal(90), IntVal(42)][..]); +} + +#[test] +fn verify_int_val_ref_is_in_maybe_empty_for_empty_slice_fails() { + let subject = IntVal(42); + + let failures = verify_that!(&subject) + .is_in_maybe_empty(&[][..]) + .display_failures(); + + assert_that!(failures).single_element().is_equal_to( + r"expected &subject to be in [] + but was: IntVal(42) + which is not in: [] +", + ); +} + +#[test] +fn verify_int_val_is_in_empty_array_fails() { + let subject = IntVal(42); + + let failures = verify_that!(subject).is_in([]).display_failures(); + + assert_that!(failures).single_element().is_equal_to( + r"expected subject to be in [] + An assertion using `is_in()` was made against an empty collection. + This would always fail, which is likely a bug in your test setup. + If an empty collection should be allowed in your test, use `is_in_maybe_empty()`. +", + ); +} diff --git a/tests/version_numbers.rs b/tests/version_numbers.rs index 247942e..36f5a67 100644 --- a/tests/version_numbers.rs +++ b/tests/version_numbers.rs @@ -1,38 +1,6 @@ //! Check the version number of this crate specified in the crate root and the //! README. - -// workaround for false positive 'unused extern crate' warnings until -// Rust issue [#95513](https://github.com/rust-lang/rust/issues/95513) is fixed -mod dummy_extern_uses { - use anyhow as _; - use asserting as _; - #[cfg(feature = "bigdecimal")] - use bigdecimal as _; - use fakeenv as _; - #[cfg(feature = "float-cmp")] - use float_cmp as _; - use hashbrown as _; - #[cfg(feature = "recursive")] - use indexmap as _; - #[cfg(feature = "num-bigint")] - use num_bigint as _; - #[cfg(any(feature = "bigdecimal", feature = "num-bigint"))] - use once_cell as _; - use proptest as _; - #[cfg(feature = "recursive")] - use rapidhash as _; - #[cfg(feature = "regex")] - use regex as _; - #[cfg(feature = "rust-decimal")] - use rust_decimal as _; - #[cfg(feature = "colored")] - use sdiff as _; - use serde as _; - use serde_bytes as _; - #[cfg(feature = "recursive")] - use serde_core as _; - use time as _; -} +#![allow(unused_crate_dependencies)] #[test] fn test_readme_deps() {