Skip to main content

fluent_assertions/assertions/
numeric_assertion.rs

1use super::Assertion;
2use num_traits::{Float, Zero};
3use std::cmp::PartialOrd;
4use std::fmt::{Debug, Display};
5use std::ops::RangeBounds;
6
7/// Specific assertions for numeric types
8impl<T> Assertion<T>
9where
10    T: PartialOrd + Display + Zero + Copy,
11{
12    /// Asserts that the value is greater than or equal to the given value
13    ///
14    /// # Examples
15    ///
16    /// ```
17    /// use fluent_assertions::*;
18    /// 5.should().be_greater_than_or_equal_to(5);
19    /// ```
20    #[track_caller]
21    pub fn be_greater_than_or_equal_to(self, other: T) -> Self {
22        assert!(
23            self.value >= other,
24            "Expected value to be greater than or equal to {}, but got {}",
25            other,
26            self.value
27        );
28        self
29    }
30
31    /// Asserts that the value is greater than the given value
32    ///
33    /// # Examples
34    ///
35    /// ```
36    /// use fluent_assertions::*;
37    /// 5.should().be_greater_than(4);
38    /// ```
39    #[track_caller]
40    pub fn be_greater_than(self, other: T) -> Self {
41        assert!(
42            self.value > other,
43            "Expected value to be greater than {}, but got {}",
44            other,
45            self.value
46        );
47        self
48    }
49
50    /// Asserts that the value is less than or equal to the given value
51    ///
52    /// # Examples
53    ///
54    /// ```
55    /// use fluent_assertions::*;
56    /// 5.should().be_less_than_or_equal_to(5);
57    /// ```
58    #[track_caller]
59    pub fn be_less_than_or_equal_to(self, other: T) -> Self {
60        assert!(
61            self.value <= other,
62            "Expected value to be less than or equal to {}, but got {}",
63            other,
64            self.value
65        );
66        self
67    }
68
69    /// Asserts that the value is less than the given value
70    ///
71    /// # Examples
72    ///
73    /// ```
74    /// use fluent_assertions::*;
75    /// 5.should().be_less_than(6);
76    /// ```
77    #[track_caller]
78    pub fn be_less_than(self, other: T) -> Self {
79        assert!(
80            self.value < other,
81            "Expected value to be less than {}, but got {}",
82            other,
83            self.value
84        );
85        self
86    }
87
88    /// Asserts that the value is positive
89    ///
90    /// # Examples
91    ///
92    /// ```
93    /// use fluent_assertions::*;
94    /// 5.should().be_positive();
95    /// ```
96    #[track_caller]
97    pub fn be_positive(self) -> Self {
98        assert!(
99            self.value > T::zero(),
100            "Expected positive value, but found {}",
101            self.value
102        );
103        self
104    }
105
106    /// Asserts that the value is negative
107    ///
108    /// # Examples
109    ///
110    /// ```
111    /// use fluent_assertions::*;
112    /// (-5).should().be_negative();
113    /// ```
114    #[track_caller]
115    pub fn be_negative(self) -> Self {
116        assert!(
117            self.value < T::zero(),
118            "Expected negative value, but found {}",
119            self.value
120        );
121        self
122    }
123
124    /// Asserts that the value is in the given range
125    ///
126    /// # Examples
127    ///
128    /// ```
129    /// use fluent_assertions::*;
130    /// 5.should().be_in_range(1..=10);
131    /// ```
132    #[track_caller]
133    pub fn be_in_range(self, range: impl RangeBounds<T> + Debug) -> Self {
134        assert!(
135            range.contains(&self.value),
136            "Expected value {} to be in range {:?}, but it wasn't",
137            self.value,
138            range
139        );
140        self
141    }
142
143    /// Asserts that the value is not in the given range
144    ///
145    /// # Examples
146    ///
147    /// ```
148    /// use fluent_assertions::*;
149    /// 20.should().not_be_in_range(1..=10);
150    /// ```
151    #[track_caller]
152    pub fn not_be_in_range(self, range: impl RangeBounds<T> + Debug) -> Self {
153        assert!(
154            !range.contains(&self.value),
155            "Expected value {} to not be in range {:?}, but it was",
156            self.value,
157            range
158        );
159        self
160    }
161}
162
163/// Specific assertions for floating-point types
164impl<T> Assertion<T>
165where
166    T: Float + Display,
167{
168    /// Asserts that the value is within `tolerance` of `expected`
169    ///
170    /// The tolerance bound is inclusive: a difference exactly equal to
171    /// `tolerance` still passes.
172    ///
173    /// # Examples
174    ///
175    /// ```
176    /// use fluent_assertions::*;
177    /// // The difference is exactly 0.5, and the inclusive bound accepts it.
178    /// 0.5_f64.should().be_close_to(1.0, 0.5);
179    /// ```
180    #[track_caller]
181    pub fn be_close_to(self, expected: T, tolerance: T) -> Self {
182        assert!(
183            (self.value - expected).abs() <= tolerance,
184            "Expected value {} to be close to {} (tolerance {}), but it wasn't",
185            self.value,
186            expected,
187            tolerance
188        );
189        self
190    }
191}
192
193#[cfg(test)]
194mod tests {
195    use crate::assertions::*;
196    use rstest::*;
197
198    #[rstest]
199    #[case(43, 42)]
200    #[case(1, 0)]
201    fn should_be_greater_and_positive(#[case] input: isize, #[case] value: isize) {
202        input.should().be_greater_than(value).be_positive();
203    }
204
205    #[rstest]
206    #[case(42.0, 41.99)]
207    #[case(1.0, 0.9)]
208    fn should_be_greater_f64(#[case] input: f64, #[case] value: f64) {
209        input.should().be_greater_than(value);
210    }
211
212    #[rstest]
213    #[case(-42)]
214    #[case(-3)]
215    fn should_be_negative_i8(#[case] input: i8) {
216        input
217            .should()
218            .be_greater_than(-43)
219            .be_negative()
220            .not_be(32)
221            .be(input);
222    }
223
224    #[test]
225    fn should_be_in_inclusive_range() {
226        5.should().be_in_range(1..=10);
227    }
228
229    #[test]
230    fn should_be_in_exclusive_range() {
231        5.should().be_in_range(1..10);
232    }
233
234    #[test]
235    #[should_panic(expected = "to be in range")]
236    fn be_in_range_panics_at_exclusive_end() {
237        10.should().be_in_range(1..10);
238    }
239
240    #[test]
241    fn should_not_be_in_range() {
242        20.should().not_be_in_range(1..=10);
243    }
244
245    #[rstest]
246    #[case(1.0f64, 1.0001, 0.001)]
247    #[case(1.0f64, 0.9999, 0.001)]
248    fn should_be_close_to_f64(#[case] input: f64, #[case] expected: f64, #[case] tolerance: f64) {
249        input.should().be_close_to(expected, tolerance);
250    }
251
252    #[rstest]
253    #[case(1.0f32, 1.05, 0.1)]
254    fn should_be_close_to_f32(#[case] input: f32, #[case] expected: f32, #[case] tolerance: f32) {
255        input.should().be_close_to(expected, tolerance);
256    }
257
258    #[test]
259    #[should_panic(expected = "to be close to")]
260    fn be_close_to_panics_when_outside_tolerance() {
261        1.0f64.should().be_close_to(1.5, 0.1);
262    }
263}