FakeItEasy expectation fail against HashSet comparisons - fakeiteasy

I am using Xamarin Studio 5.2 on Mac OS X 10.9.4 with NUnit 2.6.3 and FakeItEasy 1.23.0.
When I run tests for this code:
using System;
using ValueSet = System.Collections.Generic.HashSet<uint>;
using NUnit.Framework;
using FakeItEasy;
namespace SetTest
{
[TestFixture]
class TestFixture
{
[Test]
public void CallsUsersWithSetAndReducedSet()
{
var values = new ValueSet { 1, 2, 3 };
var setUser = A.Fake<SetUser>();
ClassUnderTest testInstance = new ClassUnderTest();
using (var scope = Fake.CreateScope())
{
testInstance.RunWith(setUser);
using (scope.OrderedAssertions())
{
A.CallTo(() => setUser.Use(A<ValueSet>.That.IsEqualTo(values))).MustHaveHappened(Repeated.Exactly.Once);
A.CallTo(() => setUser.Use(A<ValueSet>.That.Matches(set =>
set.Count == 2 && set.Contains(1)))).MustHaveHappened(Repeated.Exactly.Once);
}
}
}
}
public class SetUser
{
public virtual void Use(ValueSet set)
{
}
}
class ClassUnderTest
{
public static void Main(string[] arguments)
{
}
public void RunWith(SetUser setUser)
{
var values = new ValueSet { 1, 2, 3 };
setUser.Use(values);
values.Remove(3);
setUser.Use(values);
}
}
}
I get the following error output:
FakeItEasy.ExpectationException: Assertion failed for the following call: SetTest.SetUser.Use(1[System.UInt32]>) Expected to find it exactly once but found it #0 times among the calls:
1. SetTest.SetUser.Use(set: System.Collection.Generic.HashSet1[System.UInt32]) repeated 2 times
I don't understand what is causing this failure and how to fix it.
What is needed to get this type of test to pass?

#Tim Long is on the right track in his comment.
Here's a little more detail, as well as updates to respond to your comments of 2014-08-11 03:25:56:
The first reason the first MustHaveHappened fails:
According to the FakeItEasy argument constraints documentation, That.IsEqualTo tests for "object equality using object.Equals". That's what's causing the unexpected behaviour.
Not passing values into the method isn't necessarily a problem, or wouldn't be if ValueSet.Equals performed a value comparison, but ValueSet is a HashSet<uint>, so you can see from that class's method documentation that it doesn't—it uses object.Equals, which tests for reference equality. Thus, your IsEqualTo assertion fails. If you use a more sophisticated matcher that performed a value-type comparison for HashSet, perhaps something closer to what you use in your second A.CallTo, or maybe something using That.Contains, I think you'll have better success.
You may think to use That.IsSameSequenceAs, but be careful if doing so: the HashSet doesn't guarantee the order of the elements in the enumeration, so even if the set has the same elements, you may get a failure.
The second reason the first MustHaveHappened fails:
RunWith changes the contents of the values set between calls to setUser.Use. So the same set is used in two calls, first with 3 elements, then when it has only 2 elements. This means that by the time the first MustHaveHappened call is made, the set has only 2 elements, so the comparison fails. You could see this more clearly by writing an argument formatter for the ValueSet. That would provide more information.
The cause of the mismatch is that when a call is made to a faked method, FakeItEasy captures the arguments. However, for reference types, such as ValueSet (HashSet), only the reference to the argument is kept. Thus, if the object is modified later, in particular between the execution and the verification stages of the test, the object will look different than it did at the time of the faked call. See #jimmy_keen's answer to MustHaveHappened fails when called twice on the same object. There's a little more discussion over at FakeItEasy Issue 306 - Verifying multiple method calls with reference parameters.
In this case, the usual approach is to do as he suggests—provide code to capture the important state of the incoming argument at call time, and then query that saved state later.
You might be able to use something like this:
[Test]
public void CallsUsersWithSetAndReducedSet()
{
var capturedValueSets = new List<List<uint>>();
var setUser = A.Fake<SetUser>();
A.CallTo(() => setUser.Use(A<ValueSet>._)) // matches any call to setUser.Use
.Invokes((ValueSet theSet) => capturedValueSets.Add(theSet.ToList()));
ClassUnderTest testInstance = new ClassUnderTest();
testInstance.RunWith(setUser);
Assert.That(capturedValueSets, Has.Count.EqualTo(2),
"not enough calls to setUser.Use");
Assert.That(capturedValueSets[0], Is.EquivalentTo(new uint[] {1, 2, 3}),
"bad set passed to first call to setUser.Use");
Assert.That(capturedValueSets[1], Has.Count.EqualTo(2) & Has.Member(1),
"bad set passed to second call to setUser.Use");
}
You can see that each time Use is called, we add the contents of the ValueSet argument to capturedValueSets. Then at the end we
make sure 2 calls were made, by checking the length of capturedValueSets
make sure that the first time Use was called, the set had the elements 1, 2, and 3. Is.EquivalentTo checks the two lists but ignores order
make sure that the second time Use was called, the set had 2 elements, one of which was 1
By checking the two captured value sets in turn, all the bits about the scopes and ordered assertions became unnecessary.

Related

NullPointerException when accessing case object in map

I am receiving a NullPointerException which I believe is due to the way objects are initialised but cannot find any supporting documentation.
I have this example code which illustrates the problem in Scala 2.12.7, I have found repeatable results in Scala 3.1.3 also:
abstract class Item(val collectionName: String)
abstract class ItemCollection(val name: String)
object TechItems extends ItemCollection("tech") {
// referencing 'name' from 'ItemCollection' superclass
case object TV extends Item(collectionName = name)
val items: Map[String, Item] = Map("tv" -> TV)
}
object Test1 extends App {
// prints 'tech'
println(TechItems.items.get("tv").map(_.collectionName))
}
object Test2 extends App {
// prints 'tech'
println(TechItems.TV.collectionName)
// throws NullPointerException
println(TechItems.items.get("tv").map(_.collectionName))
}
When running Test1, the code behaves as you'd expect. When running Test2, we now receive a NullPointerException when accessing the map after accessing the TV object directly.
When I no longer reference a field from the superclass, the issue no longer occurs:
...
object TechItems extends ItemCollection("tech") {
// using String instead of reference to superclass field
case object TV extends Item(collectionName = "mycollection")
val items: Map[String, Item] = Map("tv" -> TV)
}
...
object Test2 extends App {
// prints 'mycollection'
println(TechItems.TV.collectionName)
// prints 'Some(mycollection)'
println(TechItems.items.get("tv").map(_.collectionName))
}
My current understanding of how TechItems is initialised:
We access TechItems.TV.collectionName which begins initialising TechItems
An ItemCollection("tech") is created whose fields are then available inside of TechItems (depending on access modifiers of said superclass fields)
TV is initialised and references the superclass field name
items is initialised and references TV as a value for key "tv"
I am sure that understanding is wrong but that is what I am here to learn.
My current theory for the NullPointerException:
We access TechItems.TV.collectionName which begins initialising TechItems
items is initialised alongside TV, but items captures an uninitialised TV as null
Our access to TechItems.TV.collectionName returns the value of "tech"
TechItems.items.get("tv") returns Some(null) because TV at the point of initialising items was null, due to not being initialised.
NullPointerException is thrown
To me it feels like a somewhat farfetched theory. I am sure my lack of understanding is shown here and there is an explanation in some documentation that I have failed to find. Why do I get this NullPointerException? What is the initialisation order? And why does removing the reference to a superclass field affect this initialisation?
Wow, this is a good one!
Here is what I think is going on ...
Consider this "pseudo-java" code, that I believe more-or-less accurately reflects what is actually happening in the JVM:
class TechItems extends ItemCollection {
static MODULE = new TechItems("tech")
static class TV extends Item {
static MODULE = new TV(TechItems.MODULE.name)
}
val items = Map("tv" -> TV.MODULE)
}
So, now, when you do print(TechItems.TV.MODULE.collectionName),
TechItems.MODULE gets constructed, because we need to pull name out of it to create TV.
This constructor, runs to the Map("tv" -> TV.MODULE) line, and puts null into the map (TV.MODULE is still null - we are only figuring out what to pass to its constructor.
If you use "mycollection" instead of name, it becomes
static MODULE = new TV("mycollection"), which doesn't trigger TechItems constructor.
What happens when you don't access TV before looking at items? Well, in that case, TechItems.MODULE gets initialized first, so, by the time you get to the new TV thing, as part of constructing the items, TechItems.MODULE.name is already available, so TV.MODULE can be created and put into the map.
Dima is right. In fact, without inspecting the decompiled code, it would be harder to figure out what is happening under the hood. For simplicity, let's assume you just do these 2 calls in order (it will reproduce the issue):
println(TechItems.TV) // prints 'TV'
println(TechItems.items) // prints 'Map(tv -> null)'
Now let's decompile the code and show only the relevant parts. (I removed unnecessary code to be easier to follow) First these calls:
Predef$.MODULE$.println((Object)Main.TechItems$.TV$.MODULE$);
Predef$.MODULE$.println((Object)Main.TechItems$.MODULE$.items());
This was our Main. Now TechItems and TV:
public static class TechItems$ extends ItemCollection {
public static final TechItems$ MODULE$;
private static final Map<String, Main.Item> items;
static {
MODULE$ = new TechItems$();
items = (Map)Predef$.MODULE$.Map().apply((Seq)ScalaRunTime$.MODULE$.wrapRefArray(
(Object[])new Tuple2[] {
Predef.ArrowAssoc$.MODULE$.$minus$greater$extension(
Predef$.MODULE$.ArrowAssoc((Object)"tv"), (Object)TV$.MODULE$)
}));
}
public Map<String, Main.Item> items() {
return TechItems$.items;
}
public TechItems$() {
super("tech");
}
public static class TV$ extends Main.Item implements Product, Serializable {
public static final TV$ MODULE$;
static {
Product.$init$((Product)(MODULE$ = new TV$()));
}
public TV$() {
super(TechItems$.MODULE$.name());
}
}
When calling our first println statement we trigger the evaluation of TechItems.TV which translates to TechItems$.TV$.MODULE$. The MODULE$ is just a static final reference of TV that gets initialized in the static block of TV. To get initialized, it starts executing the static block, which in turn calls TV's constructor, new TV$() which in turn triggers the call to TechItems via: super(TechItems$.MODULE$.name());
This is the part where it gets interesting: TechItems$.MODULE$ is just the static final reference of TechItems, that was not yet referenced, so it was not yet initialized. Again, in the same manner, to get initialized, the static block of TechItems gets called. But this time the static block is different: It has to initialize TechItems$.MODULE$ and items as well, because both reside in the same static block.
Since we are in the middle of initializing TV$.MODULE$, and we just called items which requires the same reference - that we have not yet finished initializing, this reference is null at this point in time, so items is executed having TV$.MODULE$ as null.
After this, the static block of TechItems$.MODULE$ finishes, the static block of TechItems.TV finishes and we get printed TV at the console. The second print becomes self-explanatory. The call to items() returns TechItems$.items that we just evaluated in the previous call to TV, so items return Map(tv -> null) which gets printed.
Observations:
Using case object TV extends Item(collectionName = name) is precisely what triggers the issue. The logical idea is that, you do not want to evaluate items before TV finishes evaluation. So one can do 2 things: 1 - either not call TV before first calling items or just TechItems - which will trigger the evaluation of TV, and thus the correct initialization of items - or 2 (better solution) - delay evaluation of items as much as possible, until you really needed.
Naturally - the solution to the second point is to make items a lazy val. If we do this, the issue goes away, because items will no longer be evaluated unless explicitly referenced by us, and it will no longer trigger evaluation when calling just TV. And if we call items first, it will trigger TV's evaluation first. I can't show you the difference in the decompiled code because only the ScalaSignature differs: keywords like lazy are implemented as "pickled" signature bytes since these are easily picked up by the JVM through reflection.
Changing it to case object TV extends Item(collectionName = "mycollection") is also a fix. Since you no longer call super(TechItems$.MODULE$.name()); from TV at all, items's evaluation is no longer triggered when just TV is called. The call to TV's constructor becomes super("mycollection"), so the second print would then correctly evaluate items to Map(tv -> TV). This is why the null goes away when you change it.
This is an example of a circular dependency: TV "kind of" needs items and items needs TV - and the order of initialization really makes the difference between a working code and a code that throws nulls at unexpected times.
Since TV is presumably initialized lazy, making items lazy as well should theoretically remove the circular dependency.
An object definition in Scala behaves much like a lazy val with an annonymous class, that gets initialized on demand, the first time it is used.
So the first instinct when you see an object inside another object, is to assume the former object will be lazily initialized (unless explicitly referenced). Because items does reference TV explicitly, even if you don't call TV explicitly, TV will be evaluated either when referencing just TechItems or directly items, whichever comes first, because both are in the same static context, as we saw.

Why am I getting 'Call to unconfigured method' when using wrapped functionality?

Using version 7.3.0 of FakeItEasy.
In following code I am getting message that call to method GetById is not configured, yet I am configuring it. What I am doing wrong?
There is no overload for the GetById method.
var fakeConnection = A.Fake<IEventStoreConnection>();
var fakeResolver = A.Fake<IEventResolver>();
var logger = A.Fake<ILogger<DummyAggregateRepository>>();
var repository = new DummyAggregateRepository(fakeConnection, fakeResolver, logger);
var fakeRepository = A.Fake<DummyAggregateRepository>(
o =>
{
o.Wrapping(repository);
o.Strict();
});
var aggregateId = Guid.NewGuid();
A.CallTo(() => fakeRepository.GetById(aggregateId, A<CancellationToken>._))
.CallsWrappedMethod();
var fixture = new AutoFixture.Fixture();
var events1 = fixture.CreateMany<DummyAggregate.EventOne>(10).Cast<Event>();
var events2 = fixture.CreateMany<DummyAggregate.EventTwo>(10).Cast<Event>();
var events = events1.Union(events2).ToList();
A.CallTo(
() => fakeRepository.GetEvents(
"dummyAggregate-" + aggregateId.ToString("N"),
aggregateId.ToString(),
A<CancellationToken>._))
.Returns(events);
var aggregate = await fakeRepository.GetById(aggregateId, default);
GetById implementation is virtual method
public virtual async Task<TAggregate> GetById(Guid aggregateId, CancellationToken ct)
{
ct.ThrowIfCancellationRequested();
var streamName = this.GetStreamName(aggregateId);
using var scope = EventStoreCommon.CreateScope(Tracer.Instance, nameof(this.GetById), streamName);
var events = await this.GetEvents(streamName, aggregateId.ToString(), ct);
if (events.Any() == false)
{
throw new AggregateNotFoundException(aggregateId, typeof(TAggregate));
}
var aggregate = new TAggregate();
foreach (var #event in events)
{
aggregate.ApplyEvent(#event);
}
return aggregate;
}
Error reported
FakeItEasy.ExpectationException: Call to unconfigured method of strict fake: MyCompany.EventStore.Test.AggregateRepositoryTests.DummyAggregateRepository...
FakeItEasy.ExpectationException
Call to unconfigured method of strict fake: MyCompany.EventStore.Test.AggregateRepositoryTests.DummyAggregateRepository.GetById(aggregateId: d8d0445d-7f82-4636-82fc-2e8f14be7f3d, ct: System.Threading.CancellationToken).
at FakeItEasy.Core.StrictFakeRule.Apply(IInterceptedFakeObjectCall fakeObjectCall) in C:\projects\fakeiteasy\src\FakeItEasy\Core\StrictFakeRule.cs:line 53
at FakeItEasy.Core.FakeManager.ApplyBestRule(IInterceptedFakeObjectCall fakeObjectCall) in C:\projects\fakeiteasy\src\FakeItEasy\Core\FakeManager.cs:line 276
at FakeItEasy.Core.FakeManager.FakeItEasy.Core.IFakeCallProcessor.Process(InterceptedFakeObjectCall fakeObjectCall) in C:\projects\fakeiteasy\src\FakeItEasy\Core\FakeManager.cs:line 178
at FakeItEasy.Creation.CastleDynamicProxy.CastleDynamicProxyGenerator.ProxyInterceptor.Intercept(IInvocation invocation) in C:\projects\fakeiteasy\src\FakeItEasy\Creation\CastleDynamicProxy\CastleDynamicProxyGenerator.cs:line 187
at Castle.DynamicProxy.AbstractInvocation.Proceed()
at Castle.Proxies.DummyAggregateRepositoryProxy.GetById(Guid aggregateId, CancellationToken ct)
at MyCompany.EventStore.Test.AggregateRepositoryTests.GetByIdTests.When_Stream_Exists_Should_Create_Instance_Of_Aggregate_With_Applied_Events() in C:\github\MyCompany_2\libraries\eventstore\test\MyCompany.EventStore.Test\AggregateRepositoryTests\GetByIdTests.cs:line 131
at System.Threading.Tasks.Task.<>c.<ThrowAsync>b__139_0(Object state)
at Xunit.Sdk.AsyncTestSyncContext.<>c__DisplayClass7_0.<Post>b__1(Object _) in C:\Dev\xunit\xunit\src\xunit.execution\Sdk\AsyncTestSyncContext.cs:line 75
Updated after getting new code and stack trace. Original answer left below
I think it's a bug.
Boring explanation:
Strict and Wrapping each add rules to the front of the faked object's rules list. The rules apply to all incoming calls. The first rule found that matches a call is triggered. So in your configuration, Wrapping is added making the Fake attempt to forward all calls to the wrapped object. Then Strict, so now the Fake will reject all calls.
You'd then expect
A.CallTo(() => fakeRepository.GetById(aggregateId, A<CancellationToken>._))
.CallsWrappedMethod();
to add a rule that would cause an incoming matching GetById call to be forwarded to the wrapped method, but the implementation of CallsWrappedMethod
appears to be missing a this.AddRuleIfNeeded() call around line 127. (You can see that CallsBaseMethod just above has this call.)
I've verified that no rule is added by the CallsWrappedMethod configuration. Likely has been broken since implemented in FakeItEasy 6.0.0 (issue #1717).
I created issue #1870 which is now fixed and released as part of FakeItEasy 7.3.1.
Original response, which was wrong, but did ask for more info:
Likely because a call was made to an unconfigured method on a strict faked repository.
We can't see DummyAggregateRepository, so there's going to be some speculation here. Based on the lack of errors earlier, I
assume that GetById is virtual or abstract, so your call should be passed to the wrapped instance.
Nothing jumps out at me as I read your question, but this sort of thing usually pops up when
a call is made to a different fake object than expected, or
a call is made to a different method than expected, usually because there's an overload and the wrong method was configured
a call is made to the expected fake object method, but with unanticipated arguments
If you edit your question, supplying
the FakeItEasy version you're using
the definition of DummyAggregateRepository, and
the entire exception that's returned, including stack traces
we may be able to help better.

Adding classes to micropython module

In reference to adding module in micropython, I was trying to create a class which has a local method. In the documentation it is given how to add local methods and that the first argument should be of mp_obj_t type which is the data struct itself. However, I was asking how can I pass extra parameters like other methods? I tried using mp_obj_t * args as second argument but STATIC MP_DEFINE_CONST_FUN_OBJ_1 gives error. I tried the same with STATIC MP_DEFINE_CONST_FUN_OBJ_VAR but it does not support passing mp_obt_t as first argument as STATIC MP_DEFINE_CONST_FUN_OBJ_VAR needs an int. I am quite new, so I was asking how to add methods to classes which can accept arguments?
You need MP_DEFINE_CONST_FUN_OBJ_2, since you have 2 arguments.
Something like
STATIC mp_obj_t my_class_func(mp_obj_t self, mp_obj_t arg) {
if (MP_OBJ_IS_SMALL_INT(lhs)) {
const mp_int_t lhs_val = MP_OBJ_SMALL_INT_VALUE(arg);
//...
} else {
//oops, not an int
}
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_2(my_class_func_obj, my_class_func);
The best source of samples like this is the source code btw.
To eleaborate on #stijn answer ~ when creating a class, all the MP_DEFINE_CONST_FUN_OBJ_XXXXXX defines work the exact same as they would if you weren't creating a class. The only difference is the first argument of ACTUAL arguments will always refer to self
Here's an example:
mp_obj_t Class_method(mp_uint_t n_args, const mp_obj_t *args) { ... }
That is the standard candidate for:
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(Class_method_obj, 1, 3, Class_method);
However, in this case args[0] will be self.
Let's have another example.
mp_obj_t Class_method(mp_uint_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { ... }
That's a prime candidate for this define
MP_DEFINE_CONST_FUN_OBJ_KW(Class_method_obj, 2, Class_method);
The only difference in this case is that the first index of allowed_args needs to automatically be handled as self. Nothing about how you do these things changes, except now the first ACTUAL argument (ie not including n_args or any other "helper" argument) needs to automatically be considered as self. That being said, you will NEVER use MP_DEFINE_CONST_FUN_OBJ_0 with a class method. '_0' means "zero arguments" and a class method will never have zero arguments because it will ALWAYS at least have self. This also means that you have to add one to however many expected arguments you have on the python end. If your python version accepts 3 arguments ~
(red, green, blue)
then your C_MODULE define has to start at 4 because it's going to get
(self, red, green, blue)

Why can't I create a callback for the List Find method in Moq?

I created an extension method that lets me treat a List as DbSet for testing purposes (actually, I found this idea in another question here on stack overflow, and it's been fairly useful). Coded as follows:
public static DbSet<T> AsDbSet<T>(this List<T> sourceList) where T : class
{
var queryable = sourceList.AsQueryable();
var mockDbSet = new Mock<DbSet<T>>();
mockDbSet.As<IQueryable<T>>().Setup(m => m.Provider).Returns(queryable.Provider);
mockDbSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(queryable.Expression);
mockDbSet.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(queryable.ElementType);
mockDbSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(queryable.GetEnumerator());
mockDbSet.Setup(d => d.Add(It.IsAny<T>())).Callback<T>(sourceList.Add);
mockDbSet.Setup(d => d.Find(It.IsAny<object[]>())).Callback(sourceList.Find);
return mockDbSet.Object;
}
I had been using Add for awhile, and that works perfectly. However, when I try to add the callback for Find, I get a compiler error saying that it can't convert a method group to an action. Why is sourceList.Add an Action, but sourceList.Find is a method group?
I'll admit I'm not particularly familiar with C# delegates, so it's likely I'm missing something very obvious. Thanks in advance.
The reason Add works is because the List<T>.Add method group contains a single method which takes a single argument of type T and returns void. This method has the same signature as an Action<T> which is one of the overloads of the Callback method (the one with a single generic type parameter, Callback<T>), therefore the List<T>.Add method group can be converted to an Action<T>.
With Find, you are trying to call the Callback method (as opposed to Callback<T>) which expects an Action parameter (as opposed to Action<T>). The difference here is that an Action does not take any parameters, but an Action<T> takes a single parameter of type T. The List<T>.Find method group cannot be converted to an Action because all the Find methods (there is only one anyway) take input parameters.
The following will compile:
public static DbSet<T> AsDbSet<T>(this List<T> sourceList) where T : class
{
var mockDbSet = new Mock<DbSet<T>>();
mockDbSet.Setup(d => d.Find(It.IsAny<object[]>())).Callback<Predicate<T>>(t => sourceList.Find(t));
return mockDbSet.Object;
}
Note that I have called .Callback<Predicate<T>> because the List<T>.Find method expects and argument of type Predicate. Also note I have had to write t => sourceList.Find(t) instead of sourceList.Find because Find returns a value (which means it doesn't match the signature of Action<Predicate<T>>). By writing it as a lambda expression the return value will be thrown away.
Note that although this compiles it will not actually work because the DbSet.Find method actually takes an object[] for it's parameter, not a Predicate<T>, so you will likely have to do something like this:
public static DbSet<T> AsDbSet<T>(this List<T> sourceList) where T : class
{
var mockDbSet = new Mock<DbSet<T>>();
mockDbSet.Setup(d => d.Find(It.IsAny<object[]>())).Callback<object[]>(keyValues => sourceList.Find(keyValues.Contains));
return mockDbSet.Object;
}
This last point has more to do with how to use the Moq library that how to use method groups, delegates and lambdas - there is all sorts of syntactic sugar going on with this line which is hiding what is actually relevant to the compiler and what isn't.

Assert.AreEqual does not use my .Equals overrides on an IEnumerable implementation

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 PagedModelclass. 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.