How do I make a method use a different class in unit tests?

The code you work on has this wonderful simple method. All it does is use a couple of other classes, passing through the parameters it received and setting some properties.

    class SimpleClass
    {
      public SimpleResult SimplePassThrough(string someString, int someInteger) {
         SimpleResult result = new SimpleResult();
         RealWork work = new RealWork();
   
         if (!work.DoActualWork(someString, someInteger)) {
           result.Error = work.Error;
         }
         return result;
      }
    }

As it is such a simple function nobody bothered to write any tests for it. After all, “It only passes through to another class.”

But now that decision has come back to bite you in the a.., because …

Hmm, never mind, the “why” doesn’t really matter anymore. You just happen to be the lucky one to bring this little beauty under test.

As simple as it is, SimplePassThrough is quite untestable.

You know how to create test doubles and use them. But whoever wrote this function, in his or her unending wisdom, decided to instantiate specific classes right there in SimplePassThrough itself. Now how are you going to make it use your test doubles to ensure that all paths in SimplePassThrough are exercised?

Even using a mocking framework there is no way to make this function use your mocks instead of the classes it specifically instantiates!

Grrrr.

Bad news. You are right. You can’t get this function under test without changing it.

Good news is, the changes you need to make are pretty straightforward.

And you don’t need any mocking framework, nor any inversion of control container to do it.

All you need to do is:

  1. Add factory methods to SimpleClass to return instances of the classes that SimplePassThrough needs to do its work.
  2. Change SimplePassThrough to use these factory methods instead of instantiating the classes itself.
  3. Add a test double for RealWork
  4. In your test code, declare a descendant of SimpleClass and override the factory methods for the instances that you want to replace with your test double.
  5. In your tests, instantiate this test descendant so the overridden versions of the factory methods will be used.

Adding factory methods

Factory methods are essentially methods that return (new) instances. The Factory Method pattern as discussed by the Gang of Four, is a little more involved using interfaces and a separate Factory class. The essence is the same: returning references to instances without the caller of the Factory (Method) knowing or caring which exact class (or even which instance) is providing the implementation.

For SimpleClass you need two factory methods. One for SimpleResult, the other for RealWork. The implementation in SimpleClass is straightforward: just return a new instance of the desired class.[1]

The methods still specify exactly which classes SimpleClass will use. Not ideal, but ok for the goal of making SimplePassThrough testable.

Both MakeResult and MakeWork are marked virtual so you can override them to get SimplePassThrough to work with instances of your test doubles.

    virtual protected SimpleResult MakeResult()
    {
        return new SimpleResult();
    }
    
    virtual protected RealWork MakeWork()
    {
        return new RealWork();
    }

Changing SimplePassThrough to use factory methods

With the factory methods in place you can change SimplePassThrough to work with whatever the factory methods provide. To achieve that, change[2]

    SimpleResult result = new SimpleResult();
    RealWork work = new RealWork();

to

    SimpleResult result = MakeResult();
    RealWork work = MakeWork();

Add a test double for RealWork

The RealWork test double class is where you ensure that all paths in SimplePassThrough can be exercised by overriding DoActualWork. If DoActualWork isn’t virtual yet in the RealWork class, you’ll have to make it so.[3]

    class RealWorkTestDouble : RealWork
    {
        override public Boolean DoActualWork(string aString, int aInt)
        {
            if (aInt != 100)
                Error = 550;

            return aInt == 100;
        }
    }

This test double ensures that you can call it with 100 in aInt to return true and with any other value to return false and set Error to 550. [4]

The class SimpleResult doesn’t need a double because it happens to be a class with just some read/write properties such as Error.

Declaring the SimpleClass test descendant

There is nothing anywhere that says you can only create descendants that will be used in production code. There also is nothing anywhere that says that you are obliged to instantiate the exact class under test in your tests for a class. In fact using a descendant class of the class under test is often a good way to break dependencies and provide “stubs” for methods called by the method under test.

So derive a class from SimpleClass, override its MakeWork method and implement that to return an instance of your RealWork test double.

    class CutDescendant : SimpleClass
    {
        override protected RealWork MakeWork()
        {
            return new RealWorkTestDouble();
        }
    }

Testing SimplePassThrough

SimplePassThrough is indeed a pretty simple function, but it still has logic that you want to stop falling over without someone noticing. So you add two tests. One to verify what should happen when RealWork.DoActualWork returns true and one for when it returns false.

    [TestMethod]
    public void SimplePassThrough_ActualWork_ReturnsTrue_ShouldReturn_NoError()
    {
        // Arrange
        SimpleClass cut = new CutDescendant();
    
        // Act
        SimpleResult result = cut.SimplePassThrough("Ok", 100);
    
        // Assert
        Assert.AreEqual(0, result.Error);
    }
    
    [TestMethod]
    public void SimplePassThrough_ActualWork_ReturnsFalse_ShouldReturn_ErrorFromWork()
    {
        // Arrange
        SimpleClass cut = new CutDescendant();
    
        // Act
        SimpleResult result = cut.SimplePassThrough("Error", 500);
    
        // Assert
        Assert.AreEqual(550, result.Error);
    }

That’s it.

Now go out there and write some tests for that method you thought was untestable

Notes

[1] Would be even better to create factory methods that return an interface instead of an object reference. That way your test doubles don’t have to derive from the original classes used and you don’t have to mark any methods in those classes virtual to provide specific test behavior.

[2] When you forget to change SimplePassThrough to use the factory methods, at least one of the tests should fail: the one checking for a non-zero Error.

[3] When you forget to make DoActualWork virtual and use override in the test descendant, at least one of the tests is likely to fail, probably the one checking for a non-zero Error.

[4] When you use different values in your Asserts than the values that control the behavior of your test doubles, then you ensure that the class you are testing really uses the value it should. Had you same value as you pass in, then your test would not detect whether SimplePassThrough set result.Error to someInteger instead of work.Error.



Leave a Reply

Your email address will not be published. Required fields are marked *

*

Show Buttons
Hide Buttons