public class SomeClass { public int MyInt { get; set; } public string MyString { get; set; } }And now imagine a test in which that SomeClass is the result of your unit tests – what assert would you write?
[TestMethod] public void CompareTwoAsserts() { var actual = new SomeClass { MyInt = 1, MyString = "str-1" }; Assert.AreEqual(1, actual.MyInt); Assert.AreEqual("str-1", actual.MyString); }Using two asserts would work, at least for a time. The problem is that failing the first assert would cause an exception to be thrown leaving us with no idea if the second would have passed or failed.
[TestMethod] public void CompareTwoObjects() { var actual = new SomeClass {MyInt = 1,MyString = "str-1"}; var expected = new SomeClass {MyInt = 1,MyString = "str-1"}; Assert.AreEqual(expected, actual); }Unfortunately it would fail. The reason is that deep down inside our assert have no idea what is an “equal” object and so it runs Object.Equals and throws an exception in case of failure. Since the default behavior of Equals is to compare references (in case of classes) the result is a fail.
[TestMethod] public void CompareOnePropertyInTwoObjects() { var actual = new SomeClass { MyInt = 1, MyString = "str-1" }; var expected = new SomeClass { MyInt = 1, MyString = "str-1" }; var fakeExpected = A.Fake<someclass>(o => o.Wrapping(expected)); A.CallTo(() => fakeExpected.Equals(A<object>._)).ReturnsLazily( call => { var other = call.GetArgument<someclass>(0); return expected.MyInt == other.MyInt; }); Assert.AreEqual(fakeExpected, actual); }What we got here is a new fake object a.k.a fakeExpected which would call custom code when its Equals method is called.
public static T ByProperties<T>(this T expected) { var fakeExpected = A.Fake<T>(o => o.Wrapping(expected)); var properties = expected.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public); A.CallTo(() => fakeExpected.Equals(A<object>._)).ReturnsLazily( call => { var actual = call.GetArgument<object>(0); if (ReferenceEquals(null, actual)) return false; if (ReferenceEquals(expected, actual)) return true; if (actual.GetType() != expected.GetType()) return false; return AreEqualByProperties(expected, actual, properties); }); return fakeExpected; } private static bool AreEqualByProperties(object expected, object actual, PropertyInfo[] properties) { foreach (var propertyInfo in properties) { var expectedValue = propertyInfo.GetValue(expected); var actualValue = propertyInfo.GetValue(actual); if (expectedValue == null || actualValue == null) { if (expectedValue != null || actualValue != null) { return false; } } else if (typeof (System.Collections.IList).IsAssignableFrom(propertyInfo.PropertyType)) { if (!AssertListsEquals((IList) expectedValue, (IList) actualValue)) { return false; } } else if (!expectedValue.Equals(actualValue)) { return false; } } return true; } private static bool AssertListsEquals(IList expectedValue, IList actualValue) { if (expectedValue.Count != actualValue.Count) { return false; } for (int I = 0; I < expectedValue.Count; I++) { if (!Equals(expectedValue[I], actualValue[I])) { return false; } } return true; }And now I can use the following to compare my expected value with the value returned by the test:
[TestMethod] public void CompareTwoObjectsByProperties() { var actual = new SomeClass { MyInt = 1, MyString = "str-1" }; var expected = new SomeClass { MyInt = 1, MyString = "str-1" }; Assert.AreEqual(expected.ByProperties(), actual); }Simple(ish) is it? I prefer this method since I no longer need to make changes to my production code (e.g. SomeClass) but I can still use plain vanilla unit testing framework.
Labels: C#, FakeItEasy, Unit tests