The Testing Context

For the past few years, everytime I’ve started a new project there has been one sure fire class that I will copy and paste in on the first day. That has been my “TestingContext”. It’s sort of this one class unit testing helper that I can’t do without. Today, I’m going to go into a bit of detail about what it is, and why I think it’s so damn awesome.

First off, let’s start about what I think beginners get wrong about unit testing, and what veterans begin to hate about unit testing.

The Problem

The number one mistake I see junior developers making when writing unit tests is that they go “outside the scope” of the class they are testing. That is, if they are testing one particular class, let’s say ServiceA, and that has a method that calls ServiceB. Your test actually should never ever enter ServiceB (There is always exceptions, but very very rare). You are testing the logic for ServiceA, so why should it go and actually run code and logic for ServiceB. Furthermore, if your tests are written for ServiceA, and ServiceB’s logic is changed, will that affect any of your tests? The answer should be no, but it often isn’t the case. So my first goal was :

Any testing helper should limit the testing scope to a single class only.

A common annoyance with unit tests is that when a constructor changes, all unit tests are probably going to go bust even if the new parameter has nothing to do with that particular unit test. I’ve seen people argue that if a constructor argument changes, that all unit tests should have to change, otherwise the service itself is obviously doing too many things. I really disagree with this. Unless you are writing pretty close to one method per class, there is always going to be times where a new service or repository is injected into a constructor that doesn’t really change anything about the existing code. If anything, sticking to SOLID principles, the class is open for extension but not modification. So the next goal was :

Changes to a constructor of a class should not require editing all tests for that class.

Next, when writing tests, you should try and limit the amount of boilerplate code in the test class itself. It’s pretty common to see a whole heap of Mock instantiations clogging up a test setup class. So much so it becomes hard to see exactly what is boilerplate and going through the motions, and what is important setup code that needs to be there. On top of that, as the class changes, I’ll often find old test classes where half of the private class level variables are assigned, but are actually never used in tests as the class was modified. So finally :

Boilerplate code within the test class should be kept to a minimum.

Building The Solution

I could explain the hell out of why I did what I did and the iterations I went through to get here, but let’s just see the code.

First, you’re gonna need to install the following Nuget package:

Install-Package AutoFixture.AutoMoq

This actually does most of the work for us. It’s an auto mocking package that means you don’t have to create mocks for every single constructor variable regardless of whether it’s used or not. You can read more about the specific package here : https://github.com/AutoFixture/AutoFixture. On it’s own it gets us pretty close to solving our problem set, but it doesn’t get us all the way there. For that we need just a tiny bit of a wrapper.

And that wrapper code :

public abstract class TestingContext<T> where T : class
{
    private Fixture fixture;
    private Dictionary<Type, Mock> injectedMocks;
    private Dictionary<Type, object> injectedConcreteClasses;

    public virtual void Setup()
    {
        fixture = new Fixture();
        fixture.Customize(new AutoMoqCustomization());

        injectedMocks = new Dictionary<Type, Mock>();
        injectedConcreteClasses = new Dictionary<Type, object>();
    }

    /// <summary>
    /// Generates a mock for a class and injects it into the final fixture
    /// </summary>
    /// <typeparam name="TMockType"></typeparam>
    /// <returns></returns>
    public Mock<TMockType> GetMockFor<TMockType>() where TMockType : class
    {
        var existingMock = injectedMocks.FirstOrDefault(x => x.Key == typeof(TMockType));
        if (existingMock.Key == null)
        {
            var newMock = new Mock<TMockType>();
            existingMock = new KeyValuePair<Type, Mock>(typeof(TMockType), newMock);
            injectedMocks.Add(existingMock.Key, existingMock.Value);
            fixture.Inject(newMock.Object);
        }
        return existingMock.Value as Mock<TMockType>;
    }

    /// <summary>
    /// Injects a concrete class to be used when generating the fixture. 
    /// </summary>
    /// <typeparam name="ClassType"></typeparam>
    /// <returns></returns>
    public void InjectClassFor<ClassType>(ClassType injectedClass) where ClassType : class
    {
        var existingClass = injectedConcreteClasses.FirstOrDefault(x => x.Key == typeof(ClassType));
        if (existingClass.Key != null)
        {
            throw new Exception($"{injectedClass.GetType().Name} has been injected more than once");
        }
        injectedConcreteClasses.Add(typeof(ClassType), injectedClass);
        fixture.Inject(injectedClass);
    }

    public T ClassUnderTest => fixture.Create<T>();
}

Essentially it’s an abstract class that your test class can inherit from, that provides a simple way to abstract away everything about mocking. It means our tests require minimal boilerplate code, and rarely has to change based on class extension. But let’s take an actual look how this thing goes.

Testing Context In Action

To show you how the testing context works, we’ll create a quick couple of test classes.

public interface ITestRepository
{
    List<string> GetNames();
}

public class TestRepository : ITestRepository
{
    public List<string> GetNames()
    {
        return new List<string>
        {
            "John",
            "Bob",
            "Wade"
        };
    }
}

public interface ITestService
{
    List<string> GetNamesExceptJohn();
}

public class TestService : ITestService
{
    private readonly ITestRepository _testRepository;
    private readonly UtilityService _utilityService;

    public TestService(ITestRepository testRepository, UtilityService utilityService)
    {
        _testRepository = testRepository;
        _utilityService = utilityService;
    }

    public List<string> GetNamesExceptJohn()
    {
        return _testRepository.GetNames().Where(x => !x.Equals("john", StringComparison.CurrentCultureIgnoreCase)).ToList();
    }

    public List<string> GetPalindromeNames()
    {
        return _testRepository.GetNames().Where(x => _utilityService.IsPalindrome(x)).ToList();
    }

}

public class UtilityService
{
    public bool IsPalindrome(string word)
    {
        return word == string.Join(string.Empty, word.ToCharArray().Reverse());
    }
}

First we just have a repository that returns names, then we have a service that has a couple of methods on it that interact with the repository, or in some cases a utility class.

Now two things here, the first being that the TestService takes in an ITestRepository (Interface) and UtilityService (class), so this could get a bit gnarly under normal circumstances because you can’t mock a concrete class. And second, the first method in the service, “GetNamesExceptJohn” doesn’t actually use this UtilityService at all. So I don’t want to have to mess about injecting in the class when it’s not going to be used at all. I would normally say you should always try and inject an interface, but in some cases if you are using a third party library that isn’t possible. So it’s more here as an example of how to get around that problem.

Now onto the tests. Our first test looks like so :

[TestFixture]
public class TestService_Tests : TestingContext<TestService>
{
    [SetUp]
    public void Setup()
    {
        base.Setup();
    }

    [Test]
    public void WhenGetNamesExceptJohnCalled_JohnIsNotReturned()
    {
        GetMockFor<ITestRepository>().Setup(x => x.GetNames()).Returns(new List<string> { "bob", "john" });

        var result = ClassUnderTest.GetNamesExceptJohn();

        Assert.IsNotNull(result);
        Assert.AreEqual(1, result.Count);
        Assert.AreEqual("bob", result.First());
    }
}

The first thing you’ll notice that we inherit from our TestingContext, and pass in exactly what class we are going to be testing. This means that it feels intuitive that the only thing we are going to be writing tests for is this single class. While it’s not impossible to run methods from other classes in here, it sort of acts as blinders to keep you focused on one single thing.

Our test setup calls the base.Setup() method which just preps up our testing context. More so, it clears out all the data from previous tests so everything is stand alone.

And finally, our actual test. We simply ask the context to get a mock for a particular interface. In the background it’s either going to return one that we created earlier (More on that shortly), or it will return a brand new one for us to setup. Then we run “ClassUnderTest” with the method we are looking to test, and that’s it! In the background it takes any mocks we have requested, and creates an instance of our class for us. We don’t have to run any constructors at all! How easy is that.

Let’s take a look at another test :

[Test]
public void WhenPalindromeNames_OnlyPalindromeNamesReturned()
{
    GetMockFor<ITestRepository>().Setup(x => x.GetNames()).Returns(new List<string> { "bob", "john" });
    InjectClassFor<UtilityService>(new UtilityService());

    var result = ClassUnderTest.GetPalindromeNames();

    Assert.IsNotNull(result);
    Assert.AreEqual(1, result.Count);
    Assert.AreEqual("bob", result.First());
}

In this test, we are doing everything pretty similar, but instead are injecting in an actual class. Again, this is not a good way to do things, you should try and be mocking as much as possible, but there are two reasons that you may have to inject a concrete class.

  1. You want to write a fake for this class instead of mocking it.
  2. You are using a library that doesn’t have an interface and so you are forced to inject the concrete class.

I think for that second one, this is more a stop gap measure. I’ve dabbled taking out the ability to inject in classes, but there is always been atleast one test per project that just needed that little bit of extensibility so I left it in.

The Niceties

There is a couple of really nice things about the context that I haven’t really pointed out too much yet, so I’ll just highlight them here.

Re-using Mocks

Under the hood, the context keeps a list of mocks you’ve already generated. This means you can reuse them without having to have private variables fly all over the place. You might have had code that looked a bit like this in the past :

var mock = new Mock<SomeService>();
mock.Setup(x => x.Get()).Returns(() => null);
mock.Setup(x => x.GetAll()).Returns(() => null);

You can now rewrite like :

GetMockFor<SomeService>().Setup(x => x.Get()).Returns(() => null);
GetMockFor<SomeService>().Setup(x => x.GetAll()).Returns(() => null);

This really comes into it’s own when your setup method typically contained a huge list of mocks that you setup at the start, then you would set a class level variable to be re-used in a method. Now you don’t have to do that at all. If you get a mock in a setup method, you can request that mock again in the actual test method.

Constructor Changes Don’t Affect Tests

You might have seen on the first test we wrote above, even though the constructor required 2 arguments, we only bothered mocking the one thing we cared about for the method under test. Everything else we can let the fixture handle.

How often do you see things like this in your tests?

var myService = new MyService(null, null, myRepositoryMock.Object, null, null);

And then someone comes along and adds a new constructor argument, and you just throw a null onto the end again? It’s a huge pain point of mine and makes tests almost unreadable at times.

Test Framework Agnostic

While in the above tests I used NUnit to write my tests, the context itself doesn’t require any particular testing framework. It can work with NUnit, MSTest and XUnit.

Revisiting Our Problems

Let’s go full circle, and revisit the problems that I found with Unit Testing.

Any testing helper should limit the testing scope to a single class only.

I think we covered this pretty well! Because we pass the class we are looking to test into our testing context, it basically abstracts away being able to call other classes.

Changes to a constructor of a class should not require editing all tests for that class.

We definitely have this. Changes to the constructor don’t affect our test, and we don’t have to setup mocks for things we are uninterested in.

Boilerplate code within the test class should be kept to a minimum.

This is definitely here. No more 50 line setup methods just setting up mocks incase we need them later. We only setup what we need, and the re-usability of mocks means that we don’t even need a bag full of private variables to hold our mock instances.

What’s Your Thoughts?

Drop a comment below with your thoughts on the testing context!

12 thoughts on “The Testing Context”

  1. I think adding a required parameter to a constructor DOES violate the Open/Closed principle. In order to not violate the principle, at best the constructor should be overloaded such that the new parameter is optional and then missing values properly defaulted/accounted for. If this is done then the unit tests should also continue to work as well because they would not have been written with any awareness of the new functionality.

    Reply
  2. Why do you say that you should be mocking as much as possible? What benefit is there in having use cases so separated that multiple non-functional dependencies are needed to satisfy one?

    Reply
  3. Actually, this is a pretty bad idea. It’s driven by your core assumption that the class under test shouldn’t call other classes. That’s not sensible, and is one of the common traps people new to unit testing fall into.

    You should only mock when you REALLY need to (eg calling external web service, maybe – but not always -talking to a database or file system, etc). You need to avoid the situation where you are creating mocks of actual code that you have written, because then you have written the code twice. You the have the dangerous situation where you can change the actual code, and forget, or don’t realise that you need, to change the code in the mocks to exactly match the new situation. So your tests can pass when your code is failing.

    Also, test code with a lot of mocks tends towards fragility, IME. And you also tend to have far too many unnecessary interfaces.

    It is perfectly OK, often even desirable, to exercise code that is tightly coupled to the class under test. And it is OK to have some code tightly coupled, of course, because otherwise no code could call any other code. Just don’t do that with everything 🙂 Putting an interface in between 2 concrete classes does not necessarily decouple, or even loosely couple, the 2 classes BTW (if you disagree, have a longer think about how that works, maybe change a function signature in the called class).

    Reply
    • You the have the dangerous situation where you can change the actual code, and forget, or don’t realise that you need, to change the code in the mocks to exactly match the new situation. So your tests can pass when your code is failing.

      Could you give an example of this? This to me would indicate poor unit test writing, not the use of mocks.

      Putting an interface in between 2 concrete classes does not necessarily decouple, or even loosely couple, the 2 classes BTW (if you disagree, have a longer think about how that works, maybe change a function signature in the called class).

      I’m not sure this really makes sense. If you change the function signature in the called class, then it no longer implements the interface, so yes it will break. However the calling class does not break, since it only knows it’s calling an “interface” that should have that method. This is exactly what dependency inversion is all about. You are thinking about when there is a one to one mapping of classes to interfaces, not if there are multiple implementations.

      Reply
  4. Very well-written, but I think there might be a couple of issues.

    – Your mock and concrete type generation and caching are not thread-safe, which could trip you up depending on your test-runner.

    – Reusing mocks and class instances means your tests aren’t isolated. Each test will inherit the Setup().Returns() calls made by previously-run tests. Again, this is not thread safe, so your test-runner could trip you up.

    Reply
    • Hi Steve,

      Reusing mocks and class instances means your tests aren’t isolated.

      The mocks shouldn’t be re-used. In the base setup, it clears out all previous mocks, and recreates the Fixture object.

      Interesting around the thread safe-ness, I’ll take a look. So far it hasn’t tripped me up, I think because each test is run in isolation on some runners maybe?

      Reply
      • Hey Wade,

        Ahh yes, so it does – my brain told me that Setup code was running in a constructor. That’ll teach me to read properly 😛

        Steve

        Reply
  5. Thanks for sharing this, its a very useful utility class. No surprises you would get a ‘thou shalt not moq’ comment but there are plenty of situations when you may need to.

    Reply

Leave a Comment