Reputation: 11375
Currently I'm writing my unit tests by creating the object under test within each TestMethod
. The main reasons for doing this is to promote self-containment, ease of readability/debugging and also possibly tailor the dependencies during construction.
public class MyClass
{
public MyClass(ISomeService serviceA, ISomeOtherService serviceB)
{
...
}
}
[TestMethod]
public void it_should_do_something()
{
var myClass = new MyClass(serviceA, serviceB);
myClass.DoSomething();
...
}
However this is becoming a maintenance nightmare, especially when there may be many tests on the class and I want to add another dependency to the constructor. It requires me to change each line of construction in my test classes.
I'm looking to keep things DRY by initialising the object in a TestInitialize
method, which will improve maintenance whilst still keep the self-containment aspect.
private MyClass _myClass;
[TestInitialize]
public void Init()
{
_myClass = new MyClass(serviceA, serviceB);
}
The only downsides I can see to doing this is that I may need to reinitialise the object if I need a different set of dependencies to be injected. It may also be slightly less clear to read/debug to another developer looking at the code for the first time. Are there any other negatives to this approach that I'm not thinking of?
What is the best practice for this scenario?
Is it bad practice to initialise the class outside of the TestMethod?
Should I favour maintenance and the DRY principle, or keep things self-contained for readability/logical purposes?
Upvotes: 3
Views: 3019
Reputation: 35891
That tests should be: 1) readable, 2) easy to write/maintain. What does "readable" mean? It means that it minimizes the amount of jumping through the code to understand what's being done on a particular line of code.
Consider creating a factory in the test project. I think it is one of the best ways for test arrangement to be readable:
public static class MyClassFactory
{
public static MyClass Create(ISomeService serviceA, ISomeOtherService serviceB)
{
return new MyClass(serviceA, serviceB);
}
public static MyClass CreateEmpty()
{
return new MyClass();
}
//Just an example, but in general not really recommended.
//May be acceptable for simple projects, but for large ones
//remembering what does "Default" mean is an unnecessary burden
//and it somehow reduces readability.
public static MyClass CreateWithDefaultServices()
{
return new MyClass(/*...*/);
}
//...
}
(The second option will be explained later). Then you can use it like this:
[TestMethod]
public void it_should_do_something()
{
//Arrange
var myClass = MyClassFactory.Create(serviceA, serviceB);
//Act
myClass.DoSomething();
//Assert
//...
}
Note how it improves readability. Beside serviceA
and serviceB
, which I left only to conform to your example, everything you need to understand is in this line. Ideally, it would look like this:
[TestMethod]
public void it_should_do_something()
{
//Arrange
var myClass = MyClassFactory.Create(
MyServiceFactory.CreateStubWithTemperature(45),
MyOtherServiceFactory.CreateStubWithContents("test string"));
//Act
myClass.DoSomething();
//Assert
//...
}
This improves readability. Note that even though the set-ups where DRY-ied out, all the factory methods are simple and obvious. There is no need to look up their definitions - they are clear from first sight.
Can ease of writing and maintaining be improved further?
A cool idea is to provide extension methods in your test project for MyClass
to allow readable configuration:
public static MyClassTestExtensions
{
public static MyClass WithService(
this MyClass @this,
ISomeService service)
{
@this.SomeService = service; //or something like this
return @this;
}
public static MyClass WithOtherService(
this MyClass @this,
ISomeOtherService service)
{
@this.SomeOtherService = service; //or something like this
return @this;
}
}
Then of course you use it like this:
[TestMethod]
public void it_should_do_something()
{
//Arrange
var serviceA = MyServiceFactory.CreateStubWithTemperature(45);
var serviceB = MyOtherServiceFactory.CreateStubWithContents("test string");
var myClass = MyClassFactory
.CreateEmpty()
.WithService(serviceA)
.WithOtherService(serviceB);
//Act
myClass.DoSomething();
//Assert
//...
}
Of course services are not a good example of such configuration (usually object is unusable without them, so there is no default constructor provided) but for any other properties it gives you a huge advantage: with N properties to configure you maintain a clean and readable way to create your test objects with all possible configurations with N extension methods, instead of N x N factory methods and without N-long argument lists in constructor or factory method invocations.
If your class is not so convenient as assumed above, one possible solution is to subclass it, and define the factory and extensions for the subclass.
If it doesn't make sense for your object to exist with only part of its properties and they are not settable you can fluently configure a factory, and then create the object:
//serviceA and serviceB creation
var myClass = MyClassFactory
.WithServiceA(serviceA)
.WithServiceB(serviceB)
.CreateMyClass();
I hope that the code of such factory is easy to deduce so I won't write it here as this answer already seems a bit long ;)
Upvotes: 7