TDD Using Dependency Injection – Step 1. Refactoring

This post is part of a series on Test Driven Development – Using Dependency Injection

We introduced some terms previously to help describe some of the things we need to do in order to make our code more testable.  In this step we are going to start the refactor to  support Dependency Injection in the RandomMessage class.

The code that is causing us a problem when using Isolator for SharePoint is in the GetRandomMessage method.

        public string GetRandomMessage(string WebUrl)
        {
            string message = "";
            try
            {
                using (SPSite site = new SPSite(WebUrl))
                {
                    using (SPWeb web = site.OpenWeb())
                    {
                        SPList messages = web.Lists["Messages"];
                        int rndMessage = GetRandomNumber(messages.ItemCount);
                        message = messages.Items[rndMessage].Title;
                    }
                }
            }
//*... snipped for brevity ...*//
        }
    }

What we need to do is use Dependency Inversion, that is we want to allow our calling code to decide what is used to get the random number.  To achieve this we need to create an interface for a class that we will allow us set define the way get our random number.

Visual Studio was really designed with agile developers in mind.   In order to achieve our refactoring we can do the following.

Highlight the method GetRandomNumber, right click on the code and choose Refactor.  Should see something similar to the image below

image

Choose the Extract Interface option which will provide you with a dialog to define the new interface name (IRandomNumber), the .cs file and also the methods that will be extracted.

image

Click OK and Visual Studio will do some of the work for us.  It does not however do everything so we will continue to fix up the code.

1. In the newly created IRandomNumber.cs files make the interface Public

As we are doing TDD we need to review our tests as the ones for the GetRandonNumber method will now be against a new object.  You may want to split out the tests into another class or .cs file but for now we will just refactor the tests that will change.

The tests should look something like below,  the code won’t at this point compile as we don’t yet have this class.

        [Test]
        public void GetRandomNumber_PostiveRange_ReturnValueInRange()
        {
            ObjectModel.RandomNumber randomNumber = new ObjectModel.RandomNumber();
            int messageId = randomNumber.GetRandomNumber(1);
            Assert.AreEqual(0,messageId);
        }
        [Test, ExpectedException(typeof(ArgumentOutOfRangeException))]
        public void GetRandomNumber_Zero_ThrowException()
        {
            ObjectModel.RandomNumber randomNumber = new ObjectModel.RandomNumber();
            int messageId = randomNumber.GetRandomNumber(0);
        }
        [Test, ExpectedException(typeof(ArgumentOutOfRangeException))]
        public void GetRandomNumber_Negative_ThrowException()
        {
            ObjectModel.RandomNumber randomNumber = new ObjectModel.RandomNumber();
            int messageId = randomNumber.GetRandomNumber(-1);
        }
        [Test]
        public void GetRandomNumber_MaxValue_ReturnValueInRange()
        {
            ObjectModel.RandomNumber randomNumber = new ObjectModel.RandomNumber();
            int messageId = randomNumber.GetRandomNumber(int.MaxValue);
            Assert.GreaterOrEqual(messageId, 0);
            Assert.LessOrEqual(messageId, int.MaxValue);
        }

Add a new class to the UnitTest.ObjectModel project called RandomNumber.  Make this class public and implement the interface IRandomNumber.  Next we need to remove the code from the RandomMessage class.   This involves removing the implementation of the IRandomNumber interface that Visual Studio added and also moving the GetRandomNumber code into our new class which should look similar to the code below.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace UnitTest.ObjectModel
{
    public class RandomNumber : IRandomNumber
    {
        public int GetRandomNumber(int max)
        {
            if (max < 1)
                throw new ArgumentOutOfRangeException(
                    "max", max, "Maximum value must be greater than or equal to 1");
            Random rnd = new Random();
            return rnd.Next(max);
        }
    }
}

At the moment our code will be in a state of flux,  it still won’t compile as the GetRandomMessage method needs to be updated,  we will add a new instance of the RandomNumber class and call this method on this.  We’re not doing the dependency injection just yet.

Once this change is implemented we should be able to compile the UnitTest.ObjectModel project.   To compile the tests we need to comment out the call we make to mock out this method (this was the method that caused us issues testing with Isolator for SharePoint).

In the method GetRandomMessage_MultipleMessages_ReturnMessage comment out the line

//Isolate.WhenCalled(() => message.GetRandomNumber(1000)).WillReturn(99);

Our solution should now compile and running the tests all except the one above should pass.

A quick recap shows that we have refactored our solution, extracting the RandonNumber method into it’s own class based on an interface and run all the tests to show the code works as expected.   This is one of the big values of doing TDD,  we can make some major refactors in our code and know when we’re complete because the tests pass.

In the next next step we are going to use the IRandomNumber interface to implement Dependency Injection.

Step 2. Implementing dependency injection into GetRandomMessage

This entry was posted in Agile, Development, SharePoint and tagged , , . Bookmark the permalink.