TDD in Practice - Dealing with Hard-To-Test Areas

Page 2 of 2
  1. Introduction
  2. Test Doubles

Test Doubles

The concept of replacing a hard-to-test implementation with an easier to test one was originally called mocking. This term has become overloaded so Gerard Meszaros has suggested replacing it with the term Test Double of which a Mock is only one category. The term Test Double is meant to convey the idea of the Stunt Double who replaces the actor for dangerous scenes. There are two different scenarios in which we want to use a Test Double.

  • Where we need to control the inputs into a SUT, but there is no observation point for us to do so. For example, if we access a repository in our SUT to return an entity from the Db, such as a Customer, so that we can retrieve some value from the Customer, we need to have some way of controlling what is returned from our repository as that forms an input into the SUT.
  • Where we need to monitor the outputs from a SUT, but there is no observation point for us to do so. For example if we are calling an external web service from our SUT then we want to monitor the arguments sent to that service, but we don’t want to actually make the call.

Working with Indirect Inputs

Controlling the inputs from the Depended On Component (DOC) to the System Under Test (SUT) is needed when because we want to control the flow of execution within the SUT, but we do not care about the outputs to that DOC. We may want to control execution to allow the ‘happy path’ but we might want to control execution to test the error path too, by providing appropriate input.

The need to control indirect inputs occurs because where we have difficulty setting up the values returned by the DOC to the SUT. Often this occurs because the DOC is in hard-to-test area (Db, file system, across network or process/AppDomain boundary). Such a DOC should generally be represented by an abstraction – depend upon abstractions – we have seam for our test in replacing the ‘ugly stuff’ in the DOC with a test double.

There are two patterns for replacing a DOC when we need to work with indirect input: stub and fake

Stub

A stub is a light-weight implementation of an interface. With a stub we just want it to return some values in response to a method call or property access on the DOC that allows the method on the SUT to continue processing. A lot of ‘mocking’ frameworks now have first class support for stubs.

In the following example an Insurance Product is our SUT which uses the DOC of a rating service and asks the DOC for the version of the rating plan that is in force. Here we are using Rhino Mocks support for automatically generating a stub.

[TestClass]
public class Using_A_Stub_To_Replace_A_Depended_Upon_Component
{
    public IRatingService ratingService;
    [TestInitialize]
    public void Should_Use_A_Stub_To_Replace_Rating_Service()
    {
        ratingService = MockRepository.GenerateStub<IRatingService>();
    }

    [TestMethod]
    public void Should_Return_The_Version_Of_The_Product_Being_Used()
    {
        //setup
        Product product = new Product(ratingService);
        const int RATING_PLAN_VERSION = 5;
        ratingService.RatingPlanVersion = RATING_PLAN_VERSION;

        //exercise
        int ratingPlanVersion = product.GetRatingPlanVersion();

        //verify
        Assert.AreEqual(RATING_PLAN_VERSION, ratingPlanVersion, "Expected the rating plan to be the one from the rating service");
    }
}
public class Product 
{
    public IRatingService ratingService;
    public Product()
    {
        ratingService = null;
    }
    public Product(IRatingService ratingService)
    {
       this.ratingService = ratingService; 
    }
    public int GetRatingPlanVersion()
    {
       return ratingService.RatingPlanVersion;
    }
}

We use a test stub where we need to control the indirect inputs from a DOC to the SUT, but we do not need to verify the indirect outputs. A stub can be useful to force execution of certain paths in the SUT, by returning values that direct the SUT down that path. This can be useful for error testing.

Fake

A stub returns a value to the SUT from the DOC, but cannot stand in place of the DOC. It is often incomplete and usable only in the context it was set up for. A fake is in contrast is a lightweight version of the DOC. It behaves in a similar fashion to the original. We commonly use a fake where we have multiple interactions with the DOC in the SUT i.e. load and retrieve or because we need a test double across a number of test fixtures and we want to remove duplicate stubs. Fake or in-memory database and fake web service are common uses.

In this example we want to replace a service that we depend upon that manages endorsements, with a fake. Whereas the stub is dumb the fake has the semantic of the DOC. The FindEndorsements method on the fake loops through its collection of endorsements and returns those that match the required type. However, while our production implementation might source its list of endorsements from a Db, our fake just uses an in-memory collection. This isolates us from an y Db issues like shared fixture or slow tests.

[TestClass]
public class Using_A_Fake_To_Replace_A_Depended_Upon_Conponent
{
    IEndorsementService endorsementService;

    [TestInitialize]
    public void Create_A_Fake_For_The_Endorsement_Service()
    {
        endorsementService = new FakeEndorsementService();
    }
    [TestMethod]
    public void Should_Return_Count_Of_Mandatory_Endorsements()
    {
       //setup
        var product = new Product(endorsementService);
        const int NO_OF_MANDATORY_ENDORSEMENTS = 12;
        endorsementService.Endorsements.AddRange(CreateMandatoryEndorsementList(NO_OF_MANDATORY_ENDORSEMENTS));
        const int NO_OF_OPTIONAL_ENDORSEMENTS = 2;
        endorsementService.Endorsements.AddRange(CreateOptionalEndorsementList(NO_OF_OPTIONAL_ENDORSEMENTS));

       //exercise
        var mandatoryEndorsements = product.FindMandatoryEndorsements();

        //verify
        Assert.AreEqual(12, mandatoryEndorsements.Count, "Expected the number of mandatory endorsements to equal those added") ;
    }
    private List<Endorsement> CreateEndorsementList(int noOfEndorsements, EndorsementType type)
    {
        List<Endorsement> endorsements = new List<Endorsement>();
        for (int i = 0; i < noOfEndorsements; i++)
        {
            endorsements.Add(new Endorsement(type));
        }
        return endorsements;
    }
    private List<Endorsement> CreateMandatoryEndorsementList(int noOfEndorsements)
    {
        return CreateEndorsementList(noOfEndorsements, EndorsementType.Mandatory);
    }
    private IEnumerable<Endorsement> CreateOptionalEndorsementList(int noOfEndorsements)
    {
        return CreateEndorsementList(noOfEndorsements, EndorsementType.Optional);
    }
}

public class Product
{
    public IEndorsementService endorsementService;
    int productId;
    public IRatingService ratingService;

    public Product()
    {
        ratingService = null;
    }

    public Product(IRatingService ratingService)
    {
       this.ratingService = ratingService;
    }

    public Product(IEndorsementService endorsementService)
    {
        this.endorsementService = endorsementService;
    }

    public List<Endorsement> FindMandatoryEndorsements()
    {
        return endorsementService.FindEndorsements(productId, EndorsementType.Mandatory);
    }

    public int GetRatingPlanVersion()
    {
       return ratingService.RatingPlanVersion;
    }
}
public class FakeEndorsementService : IEndorsementService 
{
    private List<Endorsement> endorsements;
    public List<Endorsement> Endorsements
    {
        get
        {
            return endorsements;
        }
        set
        {
            endorsements = value;
        }
    }

    public List<Endorsement> FindEndorsements(int productId, EndorsementType endorsementType)
    {
        List<Endorsement> matchingEndorsements = new List<Endorsement>();
        foreach (Endorsement endorsement in endorsements)
        {
            if (endorsement.Type == endorsementType)
                matchingEndorsements.Add(endorsement);
        }
        return matchingEndorsements;
    }
 }

Checking on Indirect Outputs

We need to check outputs where what we send to the DOC needs to be tested. Usually this occurs because we have no means to retrieve the message that was sent to the DOC by the SUT in the verification phase of our test, but need to verify that message to ensure that our code works correctly. In other words we are not testing a change in state to our SUT, but a message sent to a DOC.

When we need to record the messages from the SUT to the DOC there are two options: a spy and a mock.

Spy

A spy allows us to record the changed state of the DOC and then use an assertion to confirm that state within our test.

As such it dovetails well with the normal test model. A spy is especially valuable where we do cannot predict all of the values passed to the DOC ahead of time, and instead need to confirm them, often against the state of the SUT when the test has finished exercising the code.

A self-shunt can be a quick way to implement a spy. The test fixture implements the interface for the DOC and records any interactions in fields on the class. Confirmation then involves testing the fields. Be aware that shared fixture state means that you need to use an explicit setup to clear that shared state if more than one test uses the same fixture.

In the following test we check the document job passed to the document service when we bind a document. Within the demo we are just checking for the existence of the job, but in a real test we could examine the job to determine if it met our expectations of what should be generated.

[TestClass]
public class Use_A_Test_Spy_To_Replace_A_DOC : IDocumentService
{
    public DocumentJob job;
    [TestMethod]
    public void Should_Call_Document_Service_With_Document_Job()
    {
        //setup
        var submission = new Submission(this);
        //exercise
        submission.GenerateQuoteLetter();
        //verify - a real test would do more testing around what the job contains!
        Assert.IsNotNull(job);
    }

    public void FufillDocumentRequest(DocumentJob job)
    {
        this.job = job;
    }
 }

public class DocumentJob
{
}

public class Submission
{
    private IDocumentService service;

    public Submission(IDocumentService service)
    {
        this.service = service;
    }

    public void GenerateQuoteLetter()
    {
        DocumentJob job = new DocumentJob();
        service.FufillDocumentRequest(job);
    }
}

Mock

A mock is useful when we want to confirm the sequence and number of messages to the DOC, specifically the method calls and the arguments passed to them. A mock is intrusive in that it specifies the interaction that the SUT should have with the DOC and confirms that the interaction meets that specification. It is thus less amenable to refactoring because it has knowledge of the implementation of the SUT.

Mocks tend to be implemented using frameworks, so that makes them cheap to write, but the problem here can be a temptation to use mocks out of their appropriate context just because we have a framework for them. The issue here is we can end up with over-specified software that is difficult to refactor, because the tests lock in our implementation choices.

Using ‘loose replay’ semantics, which allow mocks to respond without raising an error if calls are not made, can reduce the coupling between specification and implementation where appropriate.

A mock differs from a spy in that a mock forces us to specify the expected interaction before we exercise the SUT, not after. It may be better to use a spy where you are uncomfortable with the test specifying the implementation of the SUT.

In this, contrived, example we call an external OFAC service to search for our customer. We don’t return the result, but set a flag which is used in later rules. We want to test our output to the OFAC service, which is third-party to confirm that our interaction is correct.

 [TestClass]
public class Use_A_Mock_To_Replace_A_DOC
{
    private IOFACService oFACService;
    private MockRepository mocks = new MockRepository();

    [TestInitialize]
    public void Create_A_Mock_OFAC_Service()
    {
        oFACService = mocks.CreateMock<IOFACService>(); //strict replay semantics
    }

    [TestMethod]
    public void Should_Call_OFAC_Service_When_Validating_Client()
    {
        //setup
        Account newClient = new Account();
        newClient.FirstName = "Ian";
        newClient.Surname = "Cooper";

        using(mocks.Record())
        {
            Expect.Call(oFACService.SearchSDNList(newClient.ToString())).Return(true);
        }

        //exercise
        using (mocks.Playback())
        {
            newClient.KnowTheCustomer(oFACService);    
        } //verify
     }
}

public class Account
{
    private bool onWatchList;
    public string FirstName {get;set;}
    public string Surname {get;set;}

    public void KnowTheCustomer(IOFACService oFACService)
    {
        onWatchList = oFACService.SearchSDNList(ToString());
    }

    public override string ToString()
    {
        return FirstName + " " + Surname;
    }

}

Inversion of Control

Note that in order to replace the DOC with a stub; we must support Dependency Injection i.e. the instance of the DOC used by the DOC must be supplied to it.

We could do this directly in our code; before we create the object we create its dependencies. The problem here is duplicating all that code to create our dependencies.

We could create a service locater that we can ask for instances of common dependencies. This removes the duplicate code, and allows us to, for example, provide different implementations of the DOC but creates an additional dependency for the class, on the Service Locater.

Finally we could use an Inversion of Control (IOC) container. With an IOC container we ask the container for the finished object. Inversion of Control is sometimes called the Hollywood Principle or “don’t call us, we’ call you”. We are using IOC in this context because we no longer have a dependency on a Service Locater, but hand off to an assembler and ask it to inject our dependencies. The framework creates us instead of us creating using the framework. Using an Inversion of Control container, such as Windsor, simplifies the creation of objects with their required dependencies.

You might also like...

Comments

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“A computer is a stupid machine with the ability to do incredibly smart things, while computer programmers are smart people with the ability to do incredibly stupid things. They are, in short, a perfect match” - Bill Bryson