When writing unit tests for classes (especially when using DI and mocks) lately I often found it handy to structure my tests to one class actually running the tests and one responsible for the setup, which helps me keeping my tests clean and readable.
The tests class will look like the following
public class MyFooBarTests
{
MyFooBarTestFixture _testFixture = new MyFooBarTestFixture();
[SetUp]
public void SetUp()
{
_testFixture.SetUp();
}
[Test]
public void ATest()
{
_testFixture.GivenRepositoryReturnsPi();
_testFixture.CallMyFancyMethod();
_testFixture.AssertQuxBaxIs(3.14159);
}
}
and what I call test fixture will look like this
internal class MyFooBarTestFixture
{
private Mock<IRepository> _repositoryMock;
private MyFooBar _cut;
public void SetUp()
{
SetUpRepositoryMock(); // elided
SetUpCut();
}
private void SetUpCut()
{
_cut = new MyFooBar(_repositoryMock.Object);
}
public void GivenRepositoryReturnsPi()
{
_repositoryMock.Setup(r => r.GetValue()).Returns(Math.PI);
}
public void CallMyFancyMethod()
{
_cut.MyFancyMethod();
}
public void AssertQuxBaxIsEqual(double expectedValue)
{
Assert.That(_cut.QuxBaz, Is.EqualTo(expectedValue)); // elided correct floating point comparison
}
}
From my point of view, this separation is really helpful. When looking at the tests class, it's crystal clear what the tests are doing, while the details of how it's done are hidden and neatly encapsulated in the test fixture. The test fixture in turn has neat and short methods.
What are the drawbacks of organizing my unit tests like this?
Some thoughts:
- I am aware that some tests will require special treatment in this paradigm
- When there are quite some mocks, the test fixture will lose cohesion, but this might be a cue that the component under test does to much anyway
- Is the emergence of this pattern a sign that my classes are already doing to much?
- Might the name test fixture be misleading? From a technical point of view it's sensible, since in other engineering fields a test fixture is the set up to run the tests, but does not necessarily run the tests itself. Anyway, in software development, the term may raise expectations that are not fulfilled by what I call test fixture.