Testing objects for equality - Error, was given a £5 note, but was expecting a £5 note

15 Aug 2019

Imagine you are at the cash register. There is a long line of customers impatient to be served waiting behind you. The cost of your shopping is £5. However, you have twelve £5 notes in your wallet, and in this alternate universe it matters which one you use to pay for your shopping. None of them can be substituted for another one.

How annoying would this be?

This is the problem that happens when we are trying to test for object equality using test assertions. Every object is different, so you can't ask if this £5 object is the same as another £5 object, because they are not the same.

The typical way around this is to look inside the £5 note and get it's value, then we do the same for the other £5 note and compare whether the values are the same. This works, but violates the encapsulation principle (and is mighty inconvenient).

In the Pragmatic Programmer, there is a story about a paper boy who knocks on your door and tells you that your paper amounts to £2. He then proceeds to open your wallet and takes your money without permission. This is a violation of your privacy (encapsulation). What the paper boy should have done is tell you that your paper cost £2. Then you could go into your own wallet, retrive your money and give it to him.

Using a test assertion to compare the values of two objects is like a paperboy who reaches into your wallet, then reaches into your neighbours wallet to compare your cash.

Testing the equality of value objects

Before you test that two objects are the same, you want to first make sure that they can be compared as the same. While two £5 note objects make sense to be considered the same, it wouldn't make sense to compare two person objects who share a name as the same, even if they were also twins. They are fundamentally different people.

Objects that we want to equate based on their values are called 'Value Objects' (see Martin Fowler). In order to understand how to change the way these objects are tested, we first need to know how most testing frameworks test for object equality.

How are objects tested for equality?

Most testing frameworks test object equality by looking at their references instead of the values that they contain. When we try to compare two objects that belong to the same class and have the same values, we get an error message that looks like the following:

Error: Was given a £5 note, but was expecting a £5 note

This is because the equality method looked at the id for the first £5 note object (it's reference), and then compared it to the id for the second £5 note. Both of these objects have references that are unique to themselves, so because the references were different, the equality test failed.

The equality method is typically defined on the object class, which all other objects inherit from (in an object-oriented language). The default equality method tests objects by their reference.

We can override the default equality method by defining our own equality method. Whenever we call a method on an object, the compiler first looks to see if the method has been defined in the object class it is being called on. If it doesn't find it there, it will look to the super class to see if it has been defined there instead.

In other words, the equality method that we define will be called instead of the default equality method that our object would otherwise inherit from.

Where should we define our equality method?

We should define our equality method on the class of the object that we want to test by value. In this case, we would define a new equality method on our £5 note class.

Every time we want to test a new object by value, we have to define a new equality method for that class of object. Even if you have a large program, it is unlikely that you would want to compare many objects by their values. Even if you did, this is a better solution (practically and in principle) than reaching into objects and comparing their values manually every time you write a new test assertion for them.

Before we write our equality method

Before we write our equality method, we need a way to access the values that we want to compare. On our £5 note class then, we would need to add an instance variable, which we might call 'amount' if we don't already have one.

What should we write in our equality method?

Inside of our equality method, we want to return whether the amount value of this object is the same as the amount value in another object of the same class.

Earlier, we said that no object should be able to reach into another object and look at it's private state (instance variables). However, if we want these two objects to be 'equal' to each other, or in other words 'one and the same', then it makes sense that they should have access to their own state across each other.

The main thing we are trying to prevent here is other objects reaching into the £5 note objects to compare their value, which is what would happen if we compared their values within the test assertion instead of from within themselves.

The equality code we would write then would look something like this:

JavaScript

  
    equals(other) {
      return this.amount === other.amount;
    }
  
  

Ruby

  
    def ==(other)
      @amount == other.amount
    end
  
  

Pharo

  
    = other
      ^ amount == other amount
  
  

Test assertion examples

After defining our equality method, we will be able to test that two different objects of the same class with the same values are equal to each other like in the following examples

JavaScript

  
    assert(fivePound1.equals(fivePound2));
  
  

Ruby

  
    fivePound1.equal?(fivePound2)
  
  

Pharo

  
    self assert: fivePound1 equals: fivePound2
  
  

Further reading