There is always a balance between readability and maintainability when writing unit tests.
I hate hearing a fellow developer say – “the task took me X time more because I had to fix 100+ tests because I refactored my production code”. This means I’m always on the look for ways to decrease the tests maintenance overhead.
While adding (or removing) a parameter to a method can cause the discomfort of fixing a few tests that run that method – doing the same to the constructor is downright painful. The constructor of the class you’re testing is the method that have to run on each test– and if you change it you must edit all of those tests so that they would compile after the change.
Over the years (and projects) I have used several strategies to reduce the pain of change – the following are the ones that worked for me:
Our test subject
I’ve decided to use the old mocking-frameworks-compare project which unfortunately has not been updated for some time now.
The project took several Mocking (read: Isolation) frameworks and compared their API using a simple problem/story.
I have a brain (business logic) that should cause Mouth to Yell if a hot Iron is touched by Hand.
Since at the time FakeItEasy was not around to be compared I’ve decided to use to
explain how we can (or rather should) create out test subject.
Option #0 - Do nothing
Been there, done that. From time to time we accept the fact that this is as good as it gets and prepare to pay the cost. I have always preferred readability in my unit tests and I still write tests where I just create the object under test.
Keep it simple means that you get to choose using a simple new to create the object under test.
[TestMethod]
public void TouchHotIron_Yell()
{
var hand = A.Fake<IHand>();
var mouth = A.Fake<IMouth>();
A.CallTo(() => hand.TouchIron(A<Iron>.Ignored)).Throws<BurnException>();
var brain = new Brain(hand, mouth);
brain.TouchIron(new Iron { IsHot = true });
A.CallTo(() => mouth.Yell()).MustHaveHappened();
}
Although simple this is not a robust solution, the object creation is tightly coupled to the test which could cause some discomfort – what happens if we add another parameter to the class constructor – sounds innocent enough, until you find yourself fixing 200 tests that happen to instantiate that object. This makes developer shy from adding parameters to constructors (seen that) or create an overload for each new parameter added just to make sure that the test code continue to compile – and then it’s my job to search the code for those useless constructors and delete them one by one.
Option #1 - Use setup method + fields
Each day a software developer who writes unit tests looks at it tests and thinks to himself – I can reduce the duplication by using the Test Setup method (in MSTest TestInitialize) and so that developer happily copies all of the initialization code to a single setup method where he can only change one method when the need arises.
[TestInitialize]
public void CreateObject()
{
_hand = A.Fake<IHand>();
_mouth = A.Fake<IMouth>();
_brain = new Brain(_hand, _mouth);
}
[TestMethod]
public void TouchHotIron_Yell()
{
A.CallTo(() => _hand.TouchIron(A<Iron>.Ignored)).Throws<BurnException>();
_brain.TouchIron(new Iron { IsHot = true });
A.CallTo(() => _mouth.Yell()).MustHaveHappened();
}
I have written about why I don’t like about using Setup methods namely they split the test logic and could can become bloated as your write more tests and your class required different initialization for each test.
If you use SetUp/TestInitialize to split the creation and testing of your objects make sure that everyone understand that this method should only be used for that purpose alone. I saw how these methods can become complex and difficult as more and more logic moves there until they are impossible to understand – you’ve been warned!
Option #2 - Factory methods
This used to be my favorite method since I still have all of test inside a single method. Instead of moving the plumbing of the initialization into the SetUp method – why not move it to a method and call it from each test that requires it.
[TestMethod]
public void TouchHotIron_Yell()
{
var hand = A.Fake<IHand>();
var mouth = A.Fake<IMouth>();
A.CallTo(() => hand.TouchIron(A<Iron>.Ignored)).Throws<BurnException>();
var brain = CreateBrain(hand, mouth);
brain.TouchIron(new Iron { IsHot = true });
A.CallTo(() => mouth.Yell()).MustHaveHappened();
}
private Brain CreateBrain(IHand hand, IMouth mouth)
{
return new Brain(hand, mouth);
}
This way when I read the test I can see everything that the test does and if I care about how the object was initialized I can dive right in.
Please make sure that you don’t do too much inside this factory method – same rules apply as in production code (Single Responsibility being one of them).
I’ve been part of one project where we’ve created a class to initialize and configure most of our business objects and it helped us make sure that we create them according to the requirements.
Option #3 - Auto-Faking container
I’ve started using Auto-Mocking containers after Typemock has added this functionality to Isolator via Isolate.Fake.Dependencies call.
In order to really decouple the creation of the test subject we need to bring in the big guns.
This is a solution similar to the way that object creation is decoupled from usage inside production code – namely Inversion of Control (IoC) container. Using an Auto-Mocking Container we can delegate the object creation logic to a specified class that is responsible to provide all of the constructor’s parameters and pass in Fake objects whenever such an argument was not specified – it’s that simple.
There’s a great blog post about it by Mark Seemann on the subject – go read it now!
In fact the container I’m using with FakeItEasy was written using Mark’s AutoFixture project.
And so by using Auto-Mocking we can make our test look like this:
private IFixture _container;
[TestInitialize]
public void InitializeContainer()
{
_container = new Fixture()
.Customize(new AutoFakeItEasyCustomization());
}
[TestMethod]
public void TouchHotIron_Yell1()
{
var hand = _container.Freeze<Fake<IHand>>().FakedObject;
var mouth = _container.Freeze<Fake<IMouth>>().FakedObject;
A.CallTo(() => hand.TouchIron(A<Iron>.Ignored)).Throws<BurnException>();
var brain = _container.Create<Brain>();
brain.TouchIron(new Iron { IsHot = true });
A.CallTo(() => mouth.Yell()).MustHaveHappened();
}
Simple? It is once you get used to it.
- Use NuGet to add AutoFixture.AutoFakeItEasy,
- Create a container using the magic words: .Customize(new AutoFakeItEasyCustomization()). This would make sure that fake objects would be passed to instances created using AutoFixture.
- Use the Freeze command to make sure that the container would return them when creating the object – I only do this inside the test so that I can use different configurations for different tests.
- And that’s it
I my object would require other parameters that I don’t care about they would be provided automatically (and recursively) by the Auto-Mocking container.
I really like using Auto-Mocking in my tests but I prefer to pass the parameters I’m using to the Create method and so I wrote a simple extensions methods that I can use to do exactly that:
public static class AutoFixtureExtensions
{
public static T FreezeFake<T>(this IFixture fixture)
{
return fixture.Freeze<Fake<T>>().FakedObject;
}
public static T Create<T, TArg1, TArg2>(this IFixture fixture, TArg1 arg1, TArg2 arg2)
{
fixture.Inject(arg1);
fixture.Inject(arg2);
return fixture.Create<T>();
}
}
I also wrote a few more extension methods for different number of arguments as well as FreezeFake for those at the team that find this API more to their liking:
[TestMethod]
public void TouchHotIron_Yell2()
{
var hand = _container.FreezeFake<IHand>();
var mouth = _container.FreezeFake<IMouth>();
A.CallTo(() => hand.TouchIron(A<Iron>.Ignored)).Throws<BurnException>();
var brain = _container.Create<Brain>();
brain.TouchIron(new Iron { IsHot = true });
A.CallTo(() => mouth.Yell()).MustHaveHappened();
}
[TestMethod]
public void TouchHotIron_Yell3()
{
var hand = A.Fake<IHand>();
var mouth = A.Fake<IMouth>();
A.CallTo(() => hand.TouchIron(A<Iron>.Ignored)).Throws<BurnException>();
var brain = _container.Create<Brain, IHand, IMouth>(hand, mouth);
brain.TouchIron(new Iron { IsHot = true });
A.CallTo(() => mouth.Yell()).MustHaveHappened();
}
Conclusion
In order to write robust unit tests you have to make sure that your tests would fail only when a bug is introduced and you definitely want to avoid compilation errors caused by the tests.
It can be very frustrating to have to fix a truckload of tests due to a trivial change – and there are practices and tools to help us avoid exactly that.
Auto-mocking container is one of these tools and since I’ve started using it my productivity has improved tremendously – so why don’t you give it a try today.
Happy coding…Labels: .NET, AutoFixture, C#, FakeItEasy, Mock objects