diff --git a/Src/FluentAssertions/AssertionExtensions.cs b/Src/FluentAssertions/AssertionExtensions.cs index 6b8559df42..ee4bc0172c 100644 --- a/Src/FluentAssertions/AssertionExtensions.cs +++ b/Src/FluentAssertions/AssertionExtensions.cs @@ -686,7 +686,7 @@ public static NumericAssertions Should(this float actualValue) [Pure] public static NullableNumericAssertions Should(this float? actualValue) { - return new NullableNumericAssertions(actualValue); + return new NullableFloatAssertions(actualValue); } /// @@ -706,7 +706,7 @@ public static NumericAssertions Should(this double actualValue) [Pure] public static NullableNumericAssertions Should(this double? actualValue) { - return new NullableNumericAssertions(actualValue); + return new NullableDoubleAssertions(actualValue); } /// diff --git a/Src/FluentAssertions/Numeric/NullableDoubleAssertions.cs b/Src/FluentAssertions/Numeric/NullableDoubleAssertions.cs new file mode 100644 index 0000000000..3ac9d75ed3 --- /dev/null +++ b/Src/FluentAssertions/Numeric/NullableDoubleAssertions.cs @@ -0,0 +1,12 @@ +namespace FluentAssertions.Numeric +{ + internal class NullableDoubleAssertions : NullableNumericAssertions + { + public NullableDoubleAssertions(double? value) + : base(value) + { + } + + private protected override bool IsNaN(double value) => double.IsNaN(value); + } +} diff --git a/Src/FluentAssertions/Numeric/NullableFloatAssertions.cs b/Src/FluentAssertions/Numeric/NullableFloatAssertions.cs new file mode 100644 index 0000000000..160bac4a9f --- /dev/null +++ b/Src/FluentAssertions/Numeric/NullableFloatAssertions.cs @@ -0,0 +1,12 @@ +namespace FluentAssertions.Numeric +{ + internal class NullableFloatAssertions : NullableNumericAssertions + { + public NullableFloatAssertions(float? value) + : base(value) + { + } + + private protected override bool IsNaN(float value) => float.IsNaN(value); + } +} diff --git a/Tests/FluentAssertions.Specs/Numeric/NullableNumericAssertionSpecs.cs b/Tests/FluentAssertions.Specs/Numeric/NullableNumericAssertionSpecs.cs index 72011ee1a6..dd29800e76 100644 --- a/Tests/FluentAssertions.Specs/Numeric/NullableNumericAssertionSpecs.cs +++ b/Tests/FluentAssertions.Specs/Numeric/NullableNumericAssertionSpecs.cs @@ -6,6 +6,444 @@ namespace FluentAssertions.Specs.Numeric { public class NullableNumericAssertionSpecs { + public class BePositive + { + [Fact] + public void NaN_is_never_a_positive_float() + { + // Arrange + float? value = float.NaN; + + // Act + Action act = () => value.Should().BePositive(); + + // Assert + act.Should().Throw().WithMessage("*but found NaN*"); + } + + [Fact] + public void NaN_is_never_a_positive_double() + { + // Arrange + double? value = double.NaN; + + // Act + Action act = () => value.Should().BePositive(); + + // Assert + act.Should().Throw().WithMessage("*but found NaN*"); + } + } + + public class BeNegative + { + [Fact] + public void NaN_is_never_a_negative_float() + { + // Arrange + float? value = float.NaN; + + // Act + Action act = () => value.Should().BeNegative(); + + // Assert + act.Should().Throw().WithMessage("*but found NaN*"); + } + + [Fact] + public void NaN_is_never_a_negative_double() + { + // Arrange + double? value = double.NaN; + + // Act + Action act = () => value.Should().BeNegative(); + + // Assert + act.Should().Throw().WithMessage("*but found NaN*"); + } + } + + public class BeLessThan + { + [Fact] + public void A_float_can_never_be_less_than_NaN() + { + // Arrange + float? value = 3.4F; + + // Act + Action act = () => value.Should().BeLessThan(float.NaN); + + // Assert + act + .Should().Throw() + .WithMessage("*NaN*"); + } + + [Fact] + public void NaN_is_never_less_than_another_float() + { + // Arrange + float? value = float.NaN; + + // Act + Action act = () => value.Should().BeLessThan(0); + + // Assert + act + .Should().Throw() + .WithMessage("*NaN*"); + } + + [Fact] + public void A_double_can_never_be_less_than_NaN() + { + // Arrange + double? value = 3.4F; + + // Act + Action act = () => value.Should().BeLessThan(double.NaN); + + // Assert + act + .Should().Throw() + .WithMessage("*NaN*"); + } + + [Fact] + public void NaN_is_never_less_than_another_double() + { + // Arrange + double? value = double.NaN; + + // Act + Action act = () => value.Should().BeLessThan(0); + + // Assert + act + .Should().Throw() + .WithMessage("*NaN*"); + } + } + + public class BeGreaterThan + { + [Fact] + public void A_float_can_never_be_greater_than_NaN() + { + // Arrange + float? value = 3.4F; + + // Act + Action act = () => value.Should().BeGreaterThan(float.NaN); + + // Assert + act + .Should().Throw() + .WithMessage("*NaN*"); + } + + [Fact] + public void NaN_is_never_greater_than_another_float() + { + // Arrange + float? value = float.NaN; + + // Act + Action act = () => value.Should().BeGreaterThan(0); + + // Assert + act + .Should().Throw() + .WithMessage("*NaN*"); + } + + [Fact] + public void A_double_can_never_be_greater_than_NaN() + { + // Arrange + double? value = 3.4F; + + // Act + Action act = () => value.Should().BeGreaterThan(double.NaN); + + // Assert + act + .Should().Throw() + .WithMessage("*NaN*"); + } + + [Fact] + public void NaN_is_never_greater_than_another_double() + { + // Arrange + double? value = double.NaN; + + // Act + Action act = () => value.Should().BeGreaterThan(0); + + // Assert + act + .Should().Throw() + .WithMessage("*NaN*"); + } + } + + public class BeLessThanOrEqualTo + { + [Fact] + public void A_float_can_never_be_less_than_or_equal_to_NaN() + { + // Arrange + float? value = 3.4F; + + // Act + Action act = () => value.Should().BeLessThanOrEqualTo(float.NaN); + + // Assert + act + .Should().Throw() + .WithMessage("*NaN*"); + } + + [Fact] + public void NaN_is_never_less_than_or_equal_to_another_float() + { + // Arrange + float? value = float.NaN; + + // Act + Action act = () => value.Should().BeLessThanOrEqualTo(0); + + // Assert + act + .Should().Throw() + .WithMessage("*NaN*"); + } + + [Fact] + public void A_double_can_never_be_less_than_or_equal_to_NaN() + { + // Arrange + double? value = 3.4; + + // Act + Action act = () => value.Should().BeLessThanOrEqualTo(double.NaN); + + // Assert + act + .Should().Throw() + .WithMessage("*NaN*"); + } + + [Fact] + public void NaN_is_never_less_than_or_equal_to_another_double() + { + // Arrange + double? value = double.NaN; + + // Act + Action act = () => value.Should().BeLessThanOrEqualTo(0); + + // Assert + act + .Should().Throw() + .WithMessage("*NaN*"); + } + } + + public class BeGreaterThanOrEqualTo + { + [Fact] + public void A_float_can_never_be_greater_than_or_equal_to_NaN() + { + // Arrange + float? value = 3.4F; + + // Act + Action act = () => value.Should().BeGreaterThanOrEqualTo(float.NaN); + + // Assert + act + .Should().Throw() + .WithMessage("*NaN*"); + } + + [Fact] + public void NaN_is_never_greater_than_or_equal_to_another_float() + { + // Arrange + float? value = float.NaN; + + // Act + Action act = () => value.Should().BeGreaterThanOrEqualTo(0); + + // Assert + act + .Should().Throw() + .WithMessage("*NaN*"); + } + + [Fact] + public void A_double_can_never_be_greater_than_or_equal_to_NaN() + { + // Arrange + double? value = 3.4; + + // Act + Action act = () => value.Should().BeGreaterThanOrEqualTo(double.NaN); + + // Assert + act + .Should().Throw() + .WithMessage("*NaN*"); + } + + [Fact] + public void NaN_is_never_greater_than_or_equal_to_another_double() + { + // Arrange + double? value = double.NaN; + + // Act + Action act = () => value.Should().BeGreaterThanOrEqualTo(0); + + // Assert + act + .Should().Throw() + .WithMessage("*NaN*"); + } + } + + public class BeInRange + { + [Theory] + [InlineData(float.NaN, 5F)] + [InlineData(5F, float.NaN)] + public void A_float_can_never_be_in_a_range_containing_NaN(float minimumValue, float maximumValue) + { + // Arrange + float? value = 4.5F; + + // Act + Action act = () => value.Should().BeInRange(minimumValue, maximumValue); + + // Assert + act + .Should().Throw() + .WithMessage( + "*NaN*"); + } + + [Fact] + public void NaN_is_never_in_range_of_two_floats() + { + // Arrange + float? value = float.NaN; + + // Act + Action act = () => value.Should().BeInRange(4, 5); + + // Assert + act + .Should().Throw() + .WithMessage( + "Expected value to be between*4* and*5*, but found*NaN*"); + } + + [Theory] + [InlineData(double.NaN, 5)] + [InlineData(5, double.NaN)] + public void A_double_can_never_be_in_a_range_containing_NaN(double minimumValue, double maximumValue) + { + // Arrange + double? value = 4.5; + + // Act + Action act = () => value.Should().BeInRange(minimumValue, maximumValue); + + // Assert + act + .Should().Throw() + .WithMessage( + "*NaN*"); + } + + [Fact] + public void NaN_is_never_in_range_of_two_doubles() + { + // Arrange + double? value = double.NaN; + + // Act + Action act = () => value.Should().BeInRange(4, 5); + + // Assert + act + .Should().Throw() + .WithMessage( + "Expected value to be between*4* and*5*, but found*NaN*"); + } + } + + public class NotBeInRange + { + [Theory] + [InlineData(float.NaN, 1F)] + [InlineData(1F, float.NaN)] + public void Cannot_use_NaN_in_a_range_of_floats(float minimumValue, float maximumValue) + { + // Arrange + float? value = 4.5F; + + // Act + Action act = () => value.Should().NotBeInRange(minimumValue, maximumValue); + + // Assert + act + .Should().Throw() + .WithMessage("*NaN*"); + } + + [Fact] + public void NaN_is_never_inside_any_range_of_floats() + { + // Arrange + float? value = float.NaN; + + // Act / Assert + value.Should().NotBeInRange(4, 5); + } + + [Theory] + [InlineData(double.NaN, 1D)] + [InlineData(1D, double.NaN)] + public void Cannot_use_NaN_in_a_range_of_doubles(double minimumValue, double maximumValue) + { + // Arrange + double? value = 4.5D; + + // Act + Action act = () => value.Should().NotBeInRange(minimumValue, maximumValue); + + // Assert + act + .Should().Throw() + .WithMessage("*NaN*"); + } + + [Fact] + public void NaN_is_never_inside_any_range_of_doubles() + { + // Arrange + double? value = double.NaN; + + // Act / Assert + value.Should().NotBeInRange(4, 5); + } + } + public class HaveValue { [Fact] @@ -222,6 +660,57 @@ public void Should_fail_with_descriptive_message_when_asserting_nullable_numeric act.Should().Throw() .WithMessage("Expected*2 because we want to test the failure message, but found 1."); } + + [Fact] + public void Nan_is_never_equal_to_a_normal_float() + { + // Arrange + float? value = float.NaN; + + // Act + Action act = () => value.Should().Be(3.4F); + + // Assert + act + .Should().Throw() + .WithMessage( + "Expected value to be *3.4F, but found NaN*"); + } + + [Fact] + public void NaN_can_be_compared_to_NaN_when_its_a_float() + { + // Arrange + float? value = float.NaN; + + // Act + value.Should().Be(float.NaN); + } + + [Fact] + public void Nan_is_never_equal_to_a_normal_double() + { + // Arrange + double? value = double.NaN; + + // Act + Action act = () => value.Should().Be(3.4D); + + // Assert + act + .Should().Throw() + .WithMessage("Expected value to be *3.4, but found NaN*"); + } + + [Fact] + public void NaN_can_be_compared_to_NaN_when_its_a_double() + { + // Arrange + double? value = double.NaN; + + // Act + value.Should().Be(double.NaN); + } } public class BeApproximately diff --git a/docs/_pages/releases.md b/docs/_pages/releases.md index c3f0021dcb..36f3a46ed7 100644 --- a/docs/_pages/releases.md +++ b/docs/_pages/releases.md @@ -17,7 +17,7 @@ sidebar: ### Fixes * `EnumAssertions.Be` did not determine the caller name - [#1835](https://github.com/fluentassertions/fluentassertions/pull/1835) * Ensure `ExcludingMissingMembers` doesn't undo usage of `WithMapping` in `BeEquivalentTo` - [#1838](https://github.com/fluentassertions/fluentassertions/pull/1838) -* Better handling of NaN in various numeric assertions - [#1822](https://github.com/fluentassertions/fluentassertions/pull/1822) +* Better handling of NaN in various numeric assertions - [#1822](https://github.com/fluentassertions/fluentassertions/pull/1822) & [#1867](https://github.com/fluentassertions/fluentassertions/pull/1867) ### Fixes (Extensibility)