Writing a unit test is all about isolating the code under test. The best way to do this is by using interfaces. An interface only described the methods and properties, but lets you change the implementation as long as you use the same signature.
The default framework objects are no exception, except there (almost) never is an interface defined. Isolation something like System.Random in your unit test can be hard. Here’s my way of doing it.
IRandom
First I’ve defined the interface for the Random object. In there are the public methods my program needs. The System.Random object does not implement this IRandom interface. I have to do this myself. More on that later.
Using this interface I can replace the Random object with a stub. More on that later too.
IRandomFactory
Another interface defines the constructors of the Random object. I’ve defined a Create method for every constructor of Random my program uses. This interface is the key to my isolation method.
Random
In my own namespace (Company.SystemIsolation) I create the Random class that implements the IRandom interface. This is the object my program is going to use.
In the Reset method the Factory property is set to the SystemRandomFactory defined below, for now all you need to know it results in using the System.Random.
The Factory property is static and is used to create an instance saved in Implementation in the constructors of the Random object.
public Random(int seed) { Implementation = Factory.Create(seed); }
Every method in the IRandom interface is implemented with a redirect to the IRandom method of the Implementation property.
public int Next() { return Implementation.Next(); }
Still with me? The hard part is over, just soak up this last two default implementations and you are in isolation zen.
SystemRandomWrapper
As stated above the System.Random object does not implement our IRandom interface. This can be solved using the wrapper design pattern.
The System.Random object is passed into the constructor to instantiate the Implementation member.
Now every call to the interface is redirected to the Implementation. Sounds familiar? Right, just like the Random object does, only the Implementation is of a different type.
SystemRandomFactory
To create a SystemRandomWrapper there must be a Factory that provides it. This implements the IRandomFactory interface by creating a System.Random object and passing it to the SystemRandomWrapper constructor. The result is a System.Random object that implements the IRandom interface.
Ioslate (optional)
For code cleanliness I’ve created a static class for replacing the Factory in the Random class. The method is called Isolate and takes an IRandomFactory parameter.
The class also contains a Reset method that calls the Reset method of the Random class. This can be expanded with the Reset method of other isolated objects.
Sample
To use the isolation in existing code do a find-and-replace of System.Random with SystemIsolation.Random. Maybe you need to fix some references, but the idea is the default behavior remains unchanged. Now you gained a way to isolate your tests from the Random object.
// ARRANGE // our fake random object to use in the unittest var fakeRandom = MockRepository.GenerateStub<IRandom>(); fakeRandom.Expect(x => x.Next()).Return(9); // our fake random factory that returns our fake random object var fakeFactory = MockRepository.GenerateStub<IRandomFactory>(); fakeFactory.Expect(x => x.Create()).Return(fakeRandom); // use our fake setup in the execution to create fake random objects SystemIoslation.Random.Factory = fakeFactory; // create your testobject and ACT and ASSERT
Take aways
To create another isolation create the two interfaces (one with the methods/properties of the object and one for the factory), then create the entry class that uses the interfaces and last create the default implementation for the two interfaces.
This all seems like a lot of work and it is. Using a code generator can do the big chunks but you will always need some manual labor. But when you are done, you can use it again and again in all your projects. I’m expanding my isolation library on every project and happily reuse my hard work from older projects.
Download the Demo project.
Pingback: System.DateTime isolation | Erictummers's Blog
why didn’t you show the Interfaces code? good post though
I kept my post small and simple. If you want the code I could post a link to it.
Still interested in seeing it. thanks.
I’ve added a demo project with all the code.
Many thanks Eric,