I have a PagedModel class which implements IEnumerable to just return the ModelData, ignoring the paging data. I have also overridden Equals and GetHashCode to allow comparing two PagedModel objects by their ModelData, PageNumber, and TotalPages, and PageSize.
Here's the problem
Dim p1 As New PagedModel() With {
.PageNumber = 1,
.PageSize = 10,
.TotalPages = 10,
.ModelData = GetModelData()
}
Dim p2 As New PagedModel() With {
.PageNumber = 1,
.PageSize = 10,
.TotalPages = 10,
.ModelData = GetModelData()
}
p1.Equals(p2) =====> True
Assert.AreEqual(p1, p2) ======> False!
It looks like NUnit is calling it's internal EnumerableEqual method to compare my PagedModel's instead of using the Equals methods I provided! Is there any way to override this behavior, or do I have to write a custom Assertion.
Doing what you are asking: I would advise against it but if you really don't like NUnit's behaviour and want to customize the assertion you can provide your own EqualityComparer.
Assert.That(p1, Is.EqualTo(p2).Using(myCustomEqualityComparer));
What you should be doing (short answer): You need GetHashCode and equals on ModelData instead of PagedModel since you are using PagedModel as the collection and ModelData as the elements.
What you should be doing (Long answer):
Instead of overriding Equals(object) on PagedModel you need to implement IEquatable<T> on ModelData, where T is the type parameter to the IEnumerable, as well as override GetHashCode(). These two methods are what all IEnumerable methods in .Net use to determine equality (for operations such as Union, Distinct etc) when using the Default Equality Comparer (you don't specify your own IEqualityComparer).
The [Default Equality Comparer] checks whether type T implements the System.IEquatable interface and, if so, returns an EqualityComparer that uses that implementation. Otherwise, it returns an EqualityComparer that uses the overrides of Object.Equals and Object.GetHashCode provided by T.
To function correctly, GetHashCode needs to return the same results for all objects that return true for .Equals(T). The reverse is not necessarily true - GetHashCode can return collisions for objects that are not equal. More information here - see Marc Gravel's accepted answer. I have also found the implementation of GetHashCode in that answer using primes very useful.
If you take a look at the implementation of the NUnit equality comparer in the GIT repo, you will see that there is a dedicated comparison block for two enumerations, which has a higher priority (simply because it is placed higher) than the comparisons using the IEquatable<T>
interface or the Object.Equals(Object)
method, which you have implemented or overloaded in your PagedModel
class.
I don't know if this is a bug or a feature, but you probably should ask yourself first, if implementing the IEnumerable<ModelData>
interface directly by your PagedModel
class is actually the best option, especially because your PagedModel
is something more than just an enumeration of ModelData
instances.
Probably it would be enough (or even better) to provide the ModelData
enumeration via a simple read-only IEnumerable<ModelData>
property of the PagedModel
class. NUnit would stop looking at your PagedModel
object as at a simple enumeration of ModelData
objects and your unit tests would behave as expected.
The only other option is the one suggested by csauve; to implement a simple custom IComparer
for your PagedModel
and to supply an instance of it to all asserts where you will compare two PagedModel
instances:
internal class PagedModelComparer : System.Collections.IComparer
{
public static readonly IComparer Instance = new PagedModelComparer();
private PagedModelComparer()
{
}
public int Compare( object x, object y )
{
return x is PagedModel && ((PagedModel)x).Equals( y );
}
}
...
[Test]
...
Assert.That( actual, Is.EqualTo( expected ).Using( PagedModelComparer.Instance ) );
...
But this will make your tests more complicated than necessary and you will always have to think to use your special comparer whenever you are writing additional tests for the PagedModel
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With