Geeks With Blogs
Boy Meets 'Hello World' Blogging the journey from College Grad to .NET Developer

I often have found that working with small, immutable objects can be very helpful. First off, they are extremely easy to test, typically not needing any sort of mocking involved. Second, due to their immutable state, you can easily override Equals to use to your advantage. The advantage I'll talk about today is the ability to introduce a new test seam that doesn't depend upon inversion of control to be able to test an object.

 

Suppose I'm writing an object that will represent a query. The query should return all blogs that contain certain text in their titles...

 

public class BlogWithTextInTitle : Query<Blog, BlogCollection>
{
    private string text;

    public BlogWithTextInTitle(string text)
    {
        this.text = text.ToLower();
    }

    public BlogCollection RunOn(IQueryable<Blog> queryable)
    {
        var results = from b in queryable
              where b.Title.ToLower().Contains(text)
              select b;
        return new BlogCollection(results.ToArray());
    }
}

 

Fun stuff! Now, suppose that I am going to write something in my service layer that will query all the blogs with the text "Hello World" in the title, and send a warning to their owner about how unimaginative their blog titles are.

(Stupid examples. I'm full of 'em.)

Now, you'll notice this query implements an interface, Query. My repository will have something like a "Find" or "RunQuery" method on it to run these queries (check out Ayende's post "Thinking about repositories" for more details on this). The service might look like this...

 

public class SendWarningService : ISendWarningService
{
    private Repository repository;

    public SendWarningService(Repository repository)
    {
        this.repository = repository;
    }

    public void SendWarningToThoseWithTextInTitle(string text, string warningMessage)
    {
        var query = null; // How do I create the query, while still being able to use it in a test?
            
        BlogCollection blogs = repository.FindAll(query);
        blogs.SendWarningToAll(warningMessage);
    }
}

 

I need to test that the blog collection returned has it's SendWarningToAll method called, but how can I control what FindAll returns? Typically, I would use a stub, but I would need to create that stub in the test method. How could I then pass that stub to the actual method? I could put a QueryFactory into the constructor, and then mock out the factory to have it return my stub, but I don't like that idea. Interestingly, we don't seem to have this problem with value types, do we? Let's assume that the query were a value type (which, I guess if you really wanted to, you could do by making it a struct, but I don't think I'm comfortable with that). Here's what the test would look like...

 

   1:  public void GivesWarningToBloggersReturnedFromQuery()
   2:  {
   3:      // Create test values.
   4:      var text = "Hello World";
   5:      var warningMessage = "Hello World? 'cmon, it's been done before!";
   6:   
   7:      var expectedQuery = new BlogWithTextInTitle(text); // Check it out, I'll just go ahead and create this thing...
   8:   
   9:      var repository = new FakeRepository();
  10:      var blogCollection = new FakeBlogCollection();
  11:   
  12:      // Set the repository to reutrn the fake blog collection when it recieves the query object.
  13:      repository.SetupResultFor(expectedQuery).Return(blogCollection);
  14:   
  15:      // Exercise the test.
  16:      var service = new WarningBlogsService(repository);
  17:      service.SendWarningToAllWithTitleContaining(text);
  18:   
  19:      // Check that the collection had it's message sent.
  20:      Assert.That(blogCollection.MessageWasSent(warningMessage), "Message was not sent.");
  21:  }

 

Notice that at no time am I ever passing the query object (expectedQuery) into the service. Instead, the service will be allowed to make another query object, simply using the new operator.

 

   1:  public class SendWarningService : ISendWarningService
   2:  {
   3:      private IRepository repository;
   4:   
   5:      public SendWarningService(IRepository repository)
   6:      {
   7:          this.repository = repository;
   8:      }
   9:   
  10:      public void SendWarningToThoseWithTextInTitle(string text, string warningMessage)
  11:      {
  12:          var query = new BlogWithTextInTitle(text);
  13:          
  14:          BlogCollection blogs = repository.FindAll(query);
  15:          blogs.SendWarningToAll(warningMessage);
  16:      }
  17:  }

 

See on line 12, I just go ahead and create the object. However, if I ran the test as it is, it would fail, because right now my fake repository (and any mock framework you use) will probably be doing the default testing of equality by reference. Obviously, the object created in my test is not equal to the one created in the actual method, because they do not reference the same object. However, by overriding Equals on our query, we can actually make this work...

 

public class BlogWithTextInTitle : Query<Blog, BlogCollection> {
    private string text;

    public BlogWithTextInTitle(string text)
    {
        this.text = text.ToLower();
    }

    public BlogCollection RunOn(IQueryable<Blog> queryable)
    {
        var results = from b in queryable
              where b.Title.ToLower().Contains(text)
              select b;
        return new BlogCollection(results.ToArray());
    }

    public override bool Equals(object obj)
    {
        var other = obj as BlogWithTextInTitle;
        if (ReferenceEquals(null, other)) return false;

        return text.Equals(other.text);
    }

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

This is sort of like using a whole new test seam. Typical test seams used are the constructors, properties, and method parameters, which are all used to pass mocks and stubs. The problem is that you can start filling these up quickly. By using value equality to your advantage, you can "pass" a stub into your system under test without using another parameter.

Now I've been bitten before by screwing up such a repetitive thing like overriding Equals. Furthermore, Testing that you've overridden Equals correctly could include a bunch of tests...

 

  • Not equal to null
  • Not equal to an object of a different type.
  • Not equal to an object of the same type with different parameters.
  • Equal to an object of the same type with same parameters.

 

That's four tests to test three lines of code (I don't test GetHashCode()... technically you could just return 0 and things would still work, just with a performance hit, but do read the comments below!). And, I've found that I do this a lot. For example, testing that you threw a custom assertion correctly is much easier when all you need to do is create the assertion yourself and check that this assertion equals the one just thrown.

So, eventually, I started needing to do these kind of tests enough that I created my own class for it. Basically, the test will test that a class adheres to the idea of Constructor Equality. This means that two objects will be considered equal if they were created by using the same constructor parameters.

 

   1:  public class ConstructorEqualityTest<T>
   2:  {
   3:      private readonly T same1;
   4:      private readonly T same2;
   5:      private readonly T[] different;
   6:   
   7:      public ConstructorEqualityTest(T same1, T same2, params T[] different)
   8:      {
   9:          this.same1 = same1;
  10:          this.same2 = same2;
  11:          this.different = different;
  12:      }
  13:   
  14:      public IList<string> Failures
  15:      {
  16:          get
  17:          {
  18:              IList<string> failures = new List<string>();
  19:                  
  20:              if (!PassesEqualToNullTest())
  21:              {
  22:                  failures.Add("Type should not be equal to null.");
  23:              }
  24:   
  25:              if (!PassesEqualToOtherTypeTest())
  26:              {
  27:                  failures.Add("Type should not be equal to a different type.");
  28:              }
  29:   
  30:              if (!PassesEqualToSameConstructorArgs())
  31:              {
  32:                  failures.Add("Type should be equal to another type constructed with the same arguments.");
  33:              }
  34:   
  35:              foreach (int indexMarkedAsSame in GetDifferentObjectsMarkedAsSame())
  36:              {
  37:                  failures.Add("Type should not be equal to another type constructed with different arguments (Different Argument " + indexMarkedAsSame + " was seen as same)");
  38:              }
  39:                  
  40:              return failures;
  41:          }
  42:      }
  43:   
  44:      public string FailureMessage
  45:      {
  46:          get
  47:          {
  48:              StringBuilder stringBuilder = new StringBuilder();
  49:              stringBuilder.AppendLine("The object did not pass all requirements for Constructor Equality.");
  50:   
  51:              foreach (string failureMessage in Failures)
  52:              {
  53:                  stringBuilder.AppendLine(failureMessage);
  54:              }
  55:   
  56:              return stringBuilder.ToString();
  57:          }
  58:      }
  59:   
  60:      private bool PassesEqualToSameConstructorArgs()
  61:      {
  62:          return same1.Equals(same2);
  63:      }
  64:   
  65:      private bool PassesEqualToNullTest()
  66:      {
  67:          return !same1.Equals(null);
  68:      }
  69:   
  70:      private bool PassesEqualToOtherTypeTest()
  71:      {
  72:          return !same1.Equals("A_String");
  73:      }
  74:   
  75:      private IList<int> GetDifferentObjectsMarkedAsSame()
  76:      {
  77:          IList<int> list = new List<int>();
  78:          for (int index = 0; index < different.Length; index++)
  79:          {
  80:              if (same1.Equals(different[index]))
  81:              {
  82:                  list.Add(index);
  83:              }
  84:          }
  85:   
  86:          return list;
  87:      }
  88:          
  89:      private bool PassesEqualToDifferentConstructorArgs()
  90:      {
  91:          return GetDifferentObjectsMarkedAsSame().Count == 0;
  92:      }
  93:   
  94:      public bool Passes()
  95:      {
  96:          return PassesEqualToNullTest() &&
  97:              PassesEqualToOtherTypeTest() &&
  98:              PassesEqualToDifferentConstructorArgs() &&
  99:              PassesEqualToSameConstructorArgs();
 100:      }
 101:  }

So now, if I wanted to test that my query works adheres to the concept of "Constructor Equality", I could use this test...

 

[Test]
public void AdheresToConstructorEquality()
{
    var same = new BlogWithTextInTitle("same");
    var same2 = new BlogWithTextInTitle("same");
    var different = new BlogWithTextInTitle("different");

    var test = new ConstructorEqualityTest<BlogWithTextInTitle>(same, same2, different);

    Assert.That(test.Passes(), test.FailureMessage);
}
 

First, I created two objects that should be equal. I'll also created additional different objects that will all be tested to ensure that Equals will return false. Each one should be different from the "same" objects from one parameter. If I had a class that had two parameters, I would use the following...

 

[Test]
public void AdheresToConstructorEquality()
{
    var same = new MyClassToTest(1 ,2);
    var same2 = new MyClassToTest(1, 2);
    var different1 = new MyClassToTest(-999, 2);
    var different2 = new MyClassToTest(1, -999);

    var test = new ConstructorEqualityTest<MyClassToTest>(same, same2, different1, different2);

    Assert.That(test.Passes(), test.FailureMessage);
}
 

My test will fail to begin with, with the failure message telling me that the objects created with the same arguments should be equal, but they aren't. Then I would override equals until my test passes.

Posted on Wednesday, April 23, 2008 10:01 PM Unit Tests | Back to top

Related Posts on Geeks With Blogs Matching Categories

Comments on this post: Developing made easier through Equals overriding.

# re: Developing made easier through Equals overriding.
Requesting Gravatar...
Immutable objects are very powerful and avoid a large number of common issues especially in multi-threaded environments.

However the overriding of Equals() on a reference type to mean VALUE equality is something that always concerns me, and does have potentially serious negative consequences.

Consider what would happen if a collection of instances was being built up. Many collecions will check if an object already is present in the collection before adding a new one. By overriding Equals() in the way you promote, distinct instances (that happened to have the same constructor parameters) would be reduced to a single entry in the collection.

This would cause a completely unexpected (and possibly hard to test/detect) condition.

Of course, items such as this must be evaluated (and documented) on a case by case basis, but my experience has shown that this information is often overlooked (especially when code moves between developers).

My preference is to have a NEW interface IValueEquality<T> which has a ValueEquals(T other) member. While this does add a little development overhead, I have repeatedly found that it avoids potential "confusiion" and certain bugs...
Left by TheCPUWizard on Apr 24, 2008 9:08 AM

# re: Developing made easier through Equals overriding.
Requesting Gravatar...
Hi, thanks for the comment.

I should probably have posted a little disclaimer about how using an object in this way could throw someone off if they are expecting the objects to behave like normal reference objects. The ValueEquals(T other) is a good alternative, I would imagine especially when you are working with a larger team. But, since I'm working mostly by myself, I have a better understanding of how the code works. Hopefully, that's enough.

Now, as for how the next guy will deal, that's a whole different issue. I'll make sure to put a link to your comment in the documentation :P
Left by Mark Hildreth on Apr 25, 2008 2:42 PM

# re: Developing made easier through Equals overriding.
Requesting Gravatar...
One of the most common reasons to override Equals is truly identify two reference type instances as equal. That is one can replace the other without consequences. A common example is a data entity that has an ID that uniquely identifies it in the database. As you've stated, .Net will treat those two instances as not equal because of the default implementation of Equals for reference types ( Object.ReferenceEquals ). Perhaps you want to make sure you don't have two instances of this object in your list. This is a perfect time to override Equals.

However, it is imperative that you also override GetHashCode correctly. Your example above will not do. The base implementation of GetHashCode is dependent on the reference once again and using the base in your implementation will give unexpected results. The hash code should be "reasonably" unique for instances that are not equal, and should always return the same result for instances that are equal. For these reasons, you should always refer to the same properties of your object in the GetHashCode as you do in Equals.

There are plenty of resources out there that show very good ways of implementing this.
Left by Will Smith on Jun 05, 2008 12:41 AM

# re: Developing made easier through Equals overriding.
Requesting Gravatar...
You're absolutely right. In fact, the reason I posted into your article was because this had already bitten me before. Didn't notice that I repeated it in the article :P

I'll make sure to fix this. Thanks.
Left by Mark Hildreth on Jun 05, 2008 1:33 AM

# re: Developing made easier through Equals overriding.
Requesting Gravatar...
Interesting,

Having smaller code (object ) made my life earier with testing

Anyway, thanks for the post
Left by web development company on Aug 17, 2009 11:15 AM

Your comment:
 (will show your gravatar)


Copyright © mhildreth | Powered by: GeeksWithBlogs.net