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
43 changes: 37 additions & 6 deletions Src/FluentAssertions/Common/ObjectExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Globalization;

namespace FluentAssertions.Common
{
Expand Down Expand Up @@ -26,21 +27,51 @@ public static bool IsSameOrEqualTo(this object actual, object expected)
return true;
}

Type expectedType = expected.GetType();
Type actualType = actual.GetType();

return actualType != expectedType
&& IsNumericType(actual)
&& IsNumericType(expected)
&& CanConvert(actual, expected, actualType, expectedType)
&& CanConvert(expected, actual, expectedType, actualType);
}

private static bool CanConvert(object source, object target, Type sourceType, Type targetType)
{
try
{
if (expected.GetType() != typeof(string) && actual.GetType() != typeof(string))
{
var convertedActual = Convert.ChangeType(actual, expected.GetType());
var converted = Convert.ChangeType(source, targetType, CultureInfo.InvariantCulture);

return convertedActual.Equals(expected);
}
return source.Equals(Convert.ChangeType(converted, sourceType, CultureInfo.InvariantCulture))
&& converted.Equals(target);
}
catch
{
// ignored
return false;
}
}

return false;
private static bool IsNumericType(object obj)
{
switch (obj)
{
case int _:
case long _:
case float _:
case double _:
case decimal _:
case sbyte _:
case byte _:
case short _:
case ushort _:
case uint _:
case ulong _:
return true;
default:
return false;
}
}
}
}
140 changes: 140 additions & 0 deletions Tests/Shared.Specs/ObjectExtensionsSpecs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FluentAssertions.Common;
using Xunit;

namespace FluentAssertions.Specs
{
public class ObjectExtensionsSpecs
{
[Theory]
[MemberData(nameof(GetNonEquivalentNumericData))]
public void When_comparing_non_equivalent_boxed_numerics_it_should_fail(object actual, object expected)
{
actual.IsSameOrEqualTo(expected).Should().BeFalse();
}

public static IEnumerable<object[]> GetNonEquivalentNumericData()
{
yield return new object[] { double.Epsilon, 0M }; // double.Epsilon cannot be represented in Decimal
yield return new object[] { 0M, double.Epsilon };
yield return new object[] { double.Epsilon, 0.3M }; // 0.3M cannot be represented in double
yield return new object[] { 0.3M, double.Epsilon };
yield return new object[] { (byte)2, 256 }; // 256 cannot be represented in byte
yield return new object[] { 256, (byte)2 };
yield return new object[] { -1, (ushort)65535 }; // 65535 is -1 casted to ushort
yield return new object[] { (ushort)65535, -1 };
yield return new object[] { 0.02d, 0 };
yield return new object[] { 0, 0.02d };
yield return new object[] { 0.02f, 0 };
yield return new object[] { 0, 0.02f };
yield return new object[] { long.MaxValue, 9.22337204E+18 };
yield return new object[] { 9.22337204E+18, long.MaxValue };
yield return new object[] { 9223372030000000000L, 9.22337204E+18 };
yield return new object[] { 9.22337204E+18, 9223372030000000000L };
}

[Theory]
[MemberData(nameof(GetNumericAndNumericData))]
public void When_comparing_a_numeric_to_a_numeric_it_should_succeed(object actual, object expected)
{
actual.IsSameOrEqualTo(expected).Should().BeTrue();
}

public static IEnumerable<object[]> GetNumericAndNumericData()
{
return from x in GetNumericIConvertibles()
from y in GetNumericIConvertibles()
select new[] { x, y };
}

[Theory]
[MemberData(nameof(GetNonNumericAndNumericData))]
public void When_comparing_a_non_numeric_to_a_numeric_it_should_fail(object actual, object unexpected)
{
// Act
bool isSameOrEquals = actual.IsSameOrEqualTo(unexpected);

// Assert
isSameOrEquals.Should().BeFalse();
}

public static IEnumerable<object[]> GetNonNumericAndNumericData()
{
return from x in GetNonNumericIConvertibles()
from y in GetNumericIConvertibles()
select new[] { x, y };
}

[Theory]
[MemberData(nameof(GetNumericAndNonNumericData))]
public void When_comparing_a_numeric_to_a_non_numeric_it_should_fail(object actual, object unexpected)
{
// Act
bool isSameOrEquals = actual.IsSameOrEqualTo(unexpected);

// Assert
isSameOrEquals.Should().BeFalse();
}

public static IEnumerable<object[]> GetNumericAndNonNumericData()
{
return from x in GetNumericIConvertibles()
from y in GetNonNumericIConvertibles()
select new[] { x, y };
}

[Theory]
[MemberData(nameof(GetNonNumericAndNonNumericData))]
public void When_comparing_a_non_numeric_to_a_non_numeric_it_should_fail(object actual, object unexpected)
{
// Act
bool isSameOrEquals = actual.IsSameOrEqualTo(unexpected);

// Assert
isSameOrEquals.Should().BeFalse();
}

public static IEnumerable<object[]> GetNonNumericAndNonNumericData()
{
object[] nonNumerics = GetNonNumericIConvertibles();
return from x in nonNumerics
from y in nonNumerics
where x != y
select new[] { x, y };
}

private static object[] GetNumericIConvertibles()
{
return new object[]
{
(byte)1,
(sbyte)1,
(short)1,
(ushort)1,
(int)1,
(uint)1,
(long)1,
(ulong)1,
(float)1,
(double)1,
(decimal)1,
};
}

private static object[] GetNonNumericIConvertibles()
{
return new object[]
{
true,
'\u0001',
new DateTime(1),
#if !NETCOREAPP1_1
DBNull.Value,
#endif
"1"
};
}
}
}
1 change: 1 addition & 0 deletions Tests/Shared.Specs/Shared.Specs.projitems
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
<Compile Include="$(MSBuildThisFileDirectory)NumericAssertionSpecs.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ObjectAssertionSpecs.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ObjectCastingSpecs.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ObjectExtensionsSpecs.cs" />
<Compile Include="$(MSBuildThisFileDirectory)PropertyInfoAssertionSpecs.cs" />
<Compile Include="$(MSBuildThisFileDirectory)PropertyInfoSelectorAssertionSpecs.cs" />
<Compile Include="$(MSBuildThisFileDirectory)PropertyInfoSelectorSpecs.cs" />
Expand Down