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 and started the refactoring of our solution in Step 1. In this step we are going to complete the refactor and implement Dependency Injection in the RandomMessage class.
There are a couple of ways to pass in the object we want to work with; we will use the Constructor as this is probably the most common and easiest to use. We have a few things that we need to do to complete the refactoring
1. Updated the tests to pass a RandomNumber object into the constructor of GetMessage
2. Update the GetMessage class to support the passing in of the RandomNumber object
3. Create a fake of the RandomNumber class that implements the IRandomNumber interface so that we can test our code
4. Confirm we have fixed our test and all tests pass.
Because we previously refactored out test code to have a setup method we only have to make one change to use the planned constructor.
[SetUp]
public void SetUp()
{
message = new ObjectModel.RandomMessage(new RandomNumber());
}
We then update our production code to support this new Constructor, it should look like this with the constructor highlighted. The GetRandomMessage method has also been updated to use the _randomNumber variable.
public class RandomMessage
{
IRandomNumber _randomNumber;
public RandomMessage(IRandomNumber randomNumber)
{
_randomNumber = randomNumber;
}
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 = _randomNumber.GetRandomNumber(messages.ItemCount);
message = messages.Items[rndMessage].Title;
}
}
}
catch (UriFormatException)
{
message = "Error: Please provide a site url for the message list.";
}
catch (ArgumentException)
{
message = "Error: Messages list could not be found";
}
catch (FileNotFoundException)
{
message = "Error: Site could not be found";
}
return message;
}
}
To get the solution to compile you can update the WebPart class with the similar change to the one in the tests.
Running the tests you should see your previous tests continue to work, showing once more the value of the tests – we refactor with confidence, we know that when done our production code still does what the developer wanted it to do.. it continues to meet the specification.
Meets the specification, what am I taking about! Doesn’t the code pass the tests?
I’ll leave this question with you for now, I will be talking about this a lot over the coming months..
Back to our Red bar which should look something like this
The observant among you will have noticed that the error has changed. We are no longer getting a warning from Isolator about unsupported objects. What we now have is a genuinely failing test. There is a chance that your test may have passed, its a slim chance, as our test was asserting that we returned the 100th message from the list. If your call to the GetRandomNumber returned 99 it will match.
Obviously this is not acceptable, and is the reason we introduced Dependency Injection. Rather than faking the return from GetRandomNumber using Isolator (mocking) we are going to pass in our object that implements the same IRandomNumber interface. If you remember back to when we Introduced some terminology we talked about loose coupling. Our GetRandomMessage class is now loosely coupled to the RandomNumber class so we can create our own, based on a common Interface, and use this instead.
We create our own test class that allows us to define the values returned from the GetRandomNumber method. You could put this in it’s own .cs file depending on how you like to structure your projects – personally I keep this in the same file as the tests that use it.
Continuing the principle of only coding what we need to our new fake class should look something like this
public class FakeRandomNumber : ObjectModel.IRandomNumber
{
#region IRandomNumber Members
public int GetRandomNumber(int max)
{
return 99;
}
#endregion
}
We have hard coded the return value of 99 as this is what we want to fake – to return the last item in the list. If we added more tests that need different values we may extend this with a simple property that allows us to set the return value within the test.
Update the test to use the FakeRandomNumber we just created, we will need to define our own instance of the RandomMessage class.
//Act
ObjectModel.RandomMessage message = new ObjectModel.RandomMessage(new FakeRandomNumber());
string rndMessage = message.GetRandomMessage("http://validsiteurl");
We have our green bar, and everything passes.
In the final part of the series we will review the changes we have made and how TDD really does help you design better code.
