Mutating Objects makes Testing Harder

In my course on TDD, one of the sections I’ll spend a lot of time on is how to write code so that it’s easier to test. This is critically important when using TDD or wanting to adopt Test Driven Development.

Object mutation takes many forms, but I’m going to focus on one in particular today: Using private members to retain state and returning that updated state partially.

Here’s an example (C# pseudocode):

​

public class Order {

  private decimal stateTax = 0.06M;
  public decimal purchasePrice = 0.0M;
  public void calculateTax(decimal purchase) {
    purchasePrice = purchase x (1.00 x stateTax);
  }
}

[Test]
public void testTaxCalculatedCorrectly() { 
  Order o = new Order();
  o.calculateTax(5.00M);
  Assert.That(o.purchasePrice, Is.EqualTo(5.30M));
}

The problem with something like this that the object’s state changes throughout the lifetime of the object, making it more difficult to test and narrow down changes. A better way to handle this (and this is something I go into a lot more depth in the course) is instead of modifying the copy you have, create a new copy, apply the changes to that new copy, and return it:

​

public class Order {

  public Order calculateTax(decimal purchase, decimal stateTax) {
    Order o = new Order();
    o.stateTax = stateTax;
    o.purchasePrice = purchase x (1.00 x stateTax);
    return o;
  }
}
[Test]
public void testTaxCalculatedCorrectly()
{
  Order o = new Order();
  Order output = o.calculateTax(5.00M, 0.06M);
  Assert.That(output.purchasePrice, Is.EqualTo(5.30M));
}


​

The performance wonks among us are going nuts, I know. Newing up a new object? Returning that? What?

The benefit here is three-fold; and these two characteristics have large ripple effects for TDD:

  • the object we passed in is not changed or mutated
  • The amount of setup we need to do to test is controllable by the test itself; which is in this case effectively a user input interface
  • We now have documentation around the properties we need to calculate tax; and we have a flex point for changing how we store tax should th eneed arise later

This is one of the principles Gary Bernhardt talks about in his talk, Boundaries, and it helps make your code more testable and easier to reason about at any given state.

I go through this and other techniques in my course on TDD for .NET Software Projects. Sign up at https://course.doubleyouproductivity.io. You’ll receive periodic updates (like this email), and you’ll get a subscriber’s only discount no one else will receive.

One thought on “Mutating Objects makes Testing Harder”

  1. For me a clue is in the name of the method: calculateTax should be renamed to AddTax or ApplyTax or something similar. I would expect calculateTax to return an amount, not an Order.

    By the way, the tests should fail: 1.00 x stateTax should be 1.00 + stateTax

Leave a Reply