Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Should I be using IEquatable to ease testing of factories?

I often work with classes that represent entities produced from a factory. To enable easy testing of my factories easily I usually implement IEquatable<T>, whilst also overriding GetHashCode and Equals (as suggested by the MSDN).

For example; take the following entity class which is simplified for example purposes. Typically my classes have a bunch more properties. Occasionally there is a also collection, which in the Equals method I check using SequenceEqual.

public class Product : IEquatable<Product>
{
    public string Name
    {
        get;
        private set;
    }

    public Product(string name)
    {
        Name = name;
    }

    public override bool Equals(object obj)
    {
        if (obj == null)
        {
            return false;
        }

        Product product = obj as Product;

        if (product == null)
        {
            return false;
        }
        else
        {
            return Equals(product);
        }
    }

    public bool Equals(Product other)
    {
        return Name == other.Name;
    }

    public override int GetHashCode()
    {
        return Name.GetHashCode();
    }
}

This means I can then do simple unit tests like so (assuming the constructor is tested elsewhere).

[TestMethod]
public void TestFactory()
{
    Product expected = new Product("James");

    Product actual = ProductFactory.BuildJames();

    Assert.AreEqual(expected, actual);
}

However this raises a number of questions.

  1. GetHashCode isn't actually used but I've spent time implementing it.
  2. I rarely actually want to use Equals in my actual application beyond testing.
  3. I spend more time writing more tests to ensure the Equals actually works correctly.
  4. I now have another three methods to maintain, e.g. add a property to the class, update methods.

But, this does give me a very neat TestMethod.

Is this an appropriate use of IEquatable, or should I take another approach?

like image 915
James Wood Avatar asked Nov 29 '15 21:11

James Wood


People also ask

When should I use IEquatable?

The IEquatable(T) interface is used by generic collection objects such as Dictionary(TKey, TValue) , List(T) , and LinkedList(T) when testing for equality in such methods as Contains , IndexOf , LastIndexOf , and Remove .

What is the point of IEquatable?

The IEquatable<T> interface defines the Equals method, which determines the equality of instances of the implementing type.


Video Answer


2 Answers

Whether this is a good idea or not really depends on what kind of type your factory creates. There are two kinds of types:

  • Types with value semantics (value types for short) and

  • Types with reference semantics (reference types for short.)

In C# it is common to use struct for value types and class for reference types, but you do not have to, you may use class for both. The point is that:

  • Value types are meant to be small, usually immutable, self-contained objects whose main purpose is to contain a certain value, while

  • Reference types are objects that have complex mutable state, possibly references to other objects, and non-trivial functionality, i.e. algorithms, business logic, etc.

If your factory is creating a value type, then sure, go ahead and make it IEquatable and use this neat trick. But in most cases, we don't use factories for value types, which tend to be rather trivial, we use factories for reference types, which tend to be rather complex, so if your factory is creating a reference type, then really, these kinds of objects are only meant to be compared by reference, so adding Equals() and GetHashCode() methods is anywhere from misleading to wrong.

Take a hint from what happens with hash maps: the presence of Equals() and GetHashCode() in a type generally means that you can use an instance of this type as a key in a hash map; but if the object is not an immutable value type, then its state may change after it has been placed in the map, in which case the GetHashCode() method will start evaluating to something else, but the hash map will never bother re-invoking GetHashCode() in order to re-position the object in the map. The result in such cases tends to be chaos.

So, the bottom line is that if your factory creates complex objects, then you should perhaps take a different approach. The obvious solution is to invoke the factory and then check each property of the returned object to make sure they are all as expected.

I could perhaps propose an improvement to this, though beware that I just thought of it, I have never tried it, so it may and may not turn out to be a good idea in practice. Here it is:

Your factory presumably creates objects that implement a particular interface. (Otherwise, what's the point of having a factory, right?) So, you could in theory stipulate that newly created instances of objects that implement this interface should have certain properties initialized to a particular set of values. This would be a rule imposed by the interface, so you could have some function tied to the interface which checks whether this is true, and this function could even be parametrized with some hint as to expect different initial values under different circumstances.

(Last I checked, in C# a method tied to an interface was usually an extension method; I do not remember off the top of my head whether C# allows static methods to be part of an interface, or whether the designers of C# have yet added to the language something as neat and elegant as the default interface methods of Java.)

So, with an extension method, it could perhaps look like this:

public boolean IsProperlyInitializedInstance( this IProduct self, String hint )
{
    if( self.Name != hint )
        return false;
    //more checks here
    return true;
}

IProduct product = productFactory.BuildJames();
Assert.IsTrue( product.IsProperlyInitializedInstance( hint:"James" ) );
like image 193
Mike Nakis Avatar answered Sep 27 '22 17:09

Mike Nakis


For test code you could use a reflection based equality, something like: Comparing object properties in c#

Many testing libraries provide such a utility, this way you don't have to change the design of your production code to suit the tests.

like image 23
chillitom Avatar answered Sep 27 '22 18:09

chillitom