views:

127

answers:

4

Working with the fine mocking-framework MoQ, I came across a somewhat surprising facet (and I don't like surprises). I'm mocking out a class which should be added to a collection after a method-call, like so:

public class SomeClass{

}

public class Container {
 private List<SomeClass> classes = new List<SomeClass>();

 public IEnumerable<SomeClass> Classes {
  get {
   return classes;
  }
 }

 public void addSomeClass(SomeClass instance) {
  classes.Add(instance);
 }
}

[Test]
public void ContainerContainsAddedClassAfterAdd() {
 var mockSomeClass = new Mock<SomeClass>(); 
 mockSomeClass.Setup(c => c.Equals(mockSomeClass.Object)).Return(true);

 var Container = new Container();
 Container.addSomeClass(mockSomeClass.Object);

 Assert(Container.Classes.Contains(mockSomeClass.Object));
}

This works well, the mock is added to the Container's collection and the setup of the Equals method on the mock makes sure the IEnumerable.Contains() return true. However there's always some complication. The class I'm really mocking out is not as simple as our SomeClass. It's something like this:

public class SomeClassOverridingEquals{
 public virtual Equals(SomeClassOverridingEquals other) {
  return false; 
 }

 public override Equals(object obj) {
  var other = obj as SomeClassOverridingEquals;

  if (other != null) return Equals(other); // calls the override
  return false;
 }
}

[Test]
public void ContainerContainsAddedClassOverridingEqualsAfterAdd() {
 var mockSomeClass = new Mock<SomeClassOverridingEquals>(); 
 mockSomeClass.Setup(c => c.Equals(mockSomeClass.Object)).Return(true);

 var Container = new Container();
 Container.addSomeClass(mockSomeClass.Object);

 Assert(Container.Classes.Contains(mockSomeClass.Object)); // fails
}

The class contains an override for the Equals method for its own specific type, and the Setup method for the mock does not seem to be able to mock out that specific method (only overriding the more general Equals(object)). Thus the test fails.

I have so far found no way of working around this quite common pattern, other than rewriting the class not to use the overriding equals.

I don't like that.

Anyone have any ideas?

A: 

Create an interface ISomeClass and make your container use interface instead. This way you'll achieve two things:

  1. You'll easily mock it by Mock<ISomeClass> and actually just unit test your container's functionality
  2. Make unite test better by actually unit test container separate from testing the actual implementation of the SomeClass class functionality.
Robert Koritnik
This is what I usually do, but interfaces are not always what you want. In the particular case leading me to this question I am using NHibernate to persist the class with the Container role here. We do not want it to contain ISomeClass, but to contain SomeClass.
Tomas
+2  A: 

I don't think the issue is with Moq, but rather with the Contains extension method. Even though you have overloaded Equals with a more specific overload, Enumerable.Contains ends up calling List<T>.Contains because the Classes property is really backed by a List<T>.

List<T>.Contains is implemented by calling EqualityComparer<T>.Default.Equals, and I think that this defaults to calling the Equals method inherited from System.Object - that is: not the one your mock overrides, but the one that takes a System.Object as input.

Perusing the implementation of EqualityComparer<T>.Default in Reflector, it seems as though it has a special case for types implementing IEquatable<T>, so if you add that interface to your class (it already has the appropriate method) it may behave differently.

Mark Seemann
+1. Reflector always has the answer. I posted a followup hack to get around this issue here: http://stackoverflow.com/questions/1476233/how-do-i-setup-a-call-of-an-equals-with-a-specific-type-overriding-the-equals-in/1486773#1486773
Anderson Imes
A: 

Mark Seemann is correct. You'll want to make sure you are hinting Moq at the right overload:

mockSomeClass.Setup(c => c.Equals((object)mockSomeClass.Object)).Return(true);
Anderson Imes
A: 

How hard is it to create an instance of SomeClass? Would it make more sense just to use the real object? If possible, that would be better since it's not changing the particular behaviour that's part of the relationship between the container and the class.

Alternatively, you could iterate through the returned collection and just look for an object that's the same as the mock instance. That would avoid having to work around any specialised behaviour.

Steve Freeman