DEV Community

Jason Sultana
Jason Sultana

Posted on • Originally published at jason.sultana.net.au on

Using the Builder Pattern to help your unit tests

G’day guys!

Have you ever had to unit test a service or facade with half a dozen (or more) dependencies? I’ll say it - dependency injection is great, but testing a service with too many dependencies sure is a pain in the assembly. Let me give you an example.

    public class UserFacade
    {
        private readonly IApiService _apiService;
        private readonly IAuthService _authService;
        private readonly IDatabaseService _databaseService;
        private readonly IEmailService _emailService;
        private readonly IFileService _fileService;
        private readonly ISessionService _sessionService;

        public UserFacade(
            IApiService apiService,
            IAuthService authService,
            IDatabaseService databaseService,
            IEmailService emailService,
            IFileService fileService,
            ISessionService sessionService
        )
        {
            _apiService = apiService;
            _authService = authService;
            _databaseService = databaseService;
            _emailService = emailService;
            _fileService = fileService;
            _sessionService = sessionService;
        }

        public void SignUp(string email, string password)
        {
            var authUser = _authService.CreateUser(email, password);
            if (authUser == null)
                throw new Exception("Unable to create auth user");

            _apiService.CreateUserInApi(authUser);

            var welcomeEmailContents = _fileService.ReadFile("template.html");
            _emailService.SendEmail(email, "Welcome!", welcomeEmailContents);
        }

        public string SignIn(string email, string password)
        {
            var authUser = _authService.GetUser(email, password);
            if (authUser == null)
                throw new UnauthorizedAccessException("User does not exist.");

            _databaseService.RecordLogin(authUser);

            return _sessionService.CreateSession(authUser);
        }
    }

Enter fullscreen mode Exit fullscreen mode

Here we have a fairly simple looking facade that allows a user to sign up, or sign in. It has 6 dependencies, which while not trivial, is not a huge number compared to what you might see in the wild. Now, how would we go about mocking those dependencies in order to write a unit test for the SignUp and SignIn methods?

At a wireframe level, that might look something like this.

    public class UserFacadeTests
    {
        private UserFacade _sut;

        [SetUp]
        public void Setup()
        {
            // Create mocks
            var apiServiceMock = new Mock<IApiService>();
            var authServiceMock = new Mock<IAuthService>();
            var databaseServiceMock = new Mock<IDatabaseService>();
            var emailServiceMock = new Mock<IEmailService>();
            var fileServiceMock = new Mock<IFileService>();
            var sessionServiceMock = new Mock<ISessionService>();

            // Setup service under test
            _sut = new UserFacade(
                apiServiceMock.Object,
                authServiceMock.Object,
                databaseServiceMock.Object,
                emailServiceMock.Object,
                fileServiceMock.Object,
                sessionServiceMock.Object
            );
        }

        [Test]
        public void Test1()
        {
            Assert.Pass();
        }

        [Test]
        public void Test2()
        {
            Assert.Pass();
        }
    }

Enter fullscreen mode Exit fullscreen mode

Let’s quickly go over some basics if this looks completely foreign to you.

What are mocks?

Mocks are on-the-fly implementations of interfaces or abstract classes that you can pass into your service for testing. In this example, I’m using a mocking library called Moq. While not shown here, Moq allows you to define callbacks and return values for properties and methods on the interface that can help verify the behaviour of the service being tested.

What’s this sut business?

sut simply means service (or system) under test. Since your test class should only be testing one service, a common convention is to name that service sut (or _sut) to identify it as being under test.

So what’s the problem?

First off, you’ll probably have more than one test class for a facade of significant size - potentially one test class for each method or distinct behaviour. This means that if the constructor signature changes, you’ll have several places to update. That’s annoying.

But secondly, notice that not all dependencies are used in each operation. The SignUp method calls _authService.CreateUser, _apiService.CreateUserInApi, _fileService.ReadFile and _emailService.SendEmail. The SignIn method, on the other hand, calls _authService.GetUser, _databaseService.RecordLogin and _sessionService.CreateSession. This means that if my test class is only testing one of these two methods, I shouldn’t actually need to provide all of the service dependencies. Further though, imagine if we had two test cases - one for when _authService.GetUser returns a value, and another when it returns null (to simulate a user not found). To support this, we’d need to create a whole new instance of _sut per test case, which means a huge amount of boilerplate code for every test.

Here’s a quick illustration of what I mean.

        [Test]
        public void Test_SignIn_UserFound()
        {
            _authServiceMock.Setup(m => m.GetUser(It.IsAny<string>(), It.IsAny<string>()))
                .Returns(new User()
                {
                    // ...
                });

            var sut = new UserFacade(_apiServiceMock.Object, _authServiceMock.Object, _databaseServiceMock.Object,
                _emailServiceMock.Object, _fileServiceMock.Object, _sessionServiceMock.Object);

            var sessionId = sut.SignIn("john.doe@gmail.com", "password");

            Assert.IsNotNull(sessionId);
        }

        [Test]
        public void Test_SignIn_UserNotFound()
        {
            _authServiceMock.Setup(m => m.GetUser(It.IsAny<string>(), It.IsAny<string>()))
                .Returns(null as User);

            var sut = new UserFacade(_apiServiceMock.Object, _authServiceMock.Object, _databaseServiceMock.Object,
                _emailServiceMock.Object, _fileServiceMock.Object, _sessionServiceMock.Object);

            var sessionId = sut.SignIn("john.doe@gmail.com", "password");

            Assert.IsNull(sessionId);
        }

Enter fullscreen mode Exit fullscreen mode

Keep in mind that this is a contrived example, containing only one mocked service. In a real test, you’ll probably need to mock multiple services, with possibly different behaviour for different test cases. In short, tests can turn very ugly very quickly.

Alright Einstein, what do you suggest?

First, let’s add a generic way for us to store dependencies on the fly.

    public class DependencyManager
    {
        private readonly Dictionary<Type, object> _dependencies = new Dictionary<Type, object>();

        public void AddDependency<T>(T dependency)
            where T : class
        {
            _dependencies[typeof(T)] = dependency;
        }

        public T GetDependency<T>()
            where T : class
        {
            if (_dependencies.ContainsKey(typeof(T)))
            {
                return _dependencies[typeof(T)] as T;
            }

            return null;
        }
    }

Enter fullscreen mode Exit fullscreen mode

I usually have a Shared test project that’s shared between all of the other test projects in a solution of significant size, which is where I would put something like this. The idea is that using this, we can register some common dependencies in the Setup stage of our test, and then register some test case specific dependencies in the test case. The same type can be registered multiple times without issue, and retrieving a dependency for a type that hasn’t been set will return null instead of producing an error. This is useful for testing code paths where a certain dependency is not required, and can in fact be null.

Next, let’s add a way for us to build an instance of the service under test (in this case, UserFacade) incrementally.

    public class UserFacadeBuilder
    {
        private readonly DependencyManager _dependencyManager = new DependencyManager();

        public UserFacadeBuilder AddDependency<T>(T value)
            where T : class
        {
            _dependencyManager.AddDependency(value);
            return this;
        }

        public UserFacade Build()
        {
            return new UserFacade(
                _dependencyManager.GetDependency<IApiService>(),
                _dependencyManager.GetDependency<IAuthService>(),
                _dependencyManager.GetDependency<IDatabaseService>(),
                _dependencyManager.GetDependency<IEmailService>(),
                _dependencyManager.GetDependency<IFileService>(),
                _dependencyManager.GetDependency<ISessionService>()
            );
        }
    }

Enter fullscreen mode Exit fullscreen mode

This component serves two purposes. Firstly, it encapsulates the newing up of the service under test into one location, so that if the constructor signature changes, we only need to change it in one place. Secondly, it provides a fluent API for registering dependencies of the service under test. Let’s see how our test class looks now.

    public class UserFacadeTests
    {
        private UserFacadeBuilder _sutBuilder;

        [SetUp]
        public void Setup()
        {
            // Create common mocks
            var apiServiceMock = new Mock<IApiService>();
            var databaseServiceMock = new Mock<IDatabaseService>();
            var emailServiceMock = new Mock<IEmailService>();
            var fileServiceMock = new Mock<IFileService>();
            var sessionServiceMock = new Mock<ISessionService>();

            _sutBuilder = new UserFacadeBuilder()
                .AddDependency(apiServiceMock.Object)
                .AddDependency(databaseServiceMock.Object)
                .AddDependency(emailServiceMock.Object)
                .AddDependency(fileServiceMock.Object)
                .AddDependency(sessionServiceMock.Object);
        }

        [Test]
        public void Test_SignIn_UserFound()
        {
            var authServiceMock = new Mock<IAuthService>();
            authServiceMock.Setup(m => m.GetUser(It.IsAny<string>(), It.IsAny<string>()))
                .Returns(new User()
                {
                    // ...
                });

            var sut = _sutBuilder
                .AddDependency(authServiceMock.Object)
                .Build();

            var sessionId = sut.SignIn("john.doe@gmail.com", "password");

            Assert.IsNotNull(sessionId);
        }

        [Test]
        public void Test_SignIn_UserNotFound()
        {
            var authServiceMock = new Mock<IAuthService>();
            authServiceMock.Setup(m => m.GetUser(It.IsAny<string>(), It.IsAny<string>()))
                .Returns(null as User);

            var sut = _sutBuilder
                .AddDependency(authServiceMock.Object)
                .Build();

            var sessionId = sut.SignIn("john.doe@gmail.com", "password");

            Assert.IsNull(sessionId);
        }
    }

Enter fullscreen mode Exit fullscreen mode

Now, each test case only registers a custom mock that’s specific to the test, as opposed to having to register all mocks every time. We also have the ability to omit dependencies that aren’t required for the code path that the test is concerned with. For example, since we’re only testing the SignIn method, IEmailService, IApiService and IFileService can be safely omitted from the builder.

I’ve found this pattern to be very helpful in making tests for life-size services with a realistic number of dependencies manageable.

What do you think? How do you deal with testing a service with 6 or more dependencies that need to be mocked? Let me know in the comments. Catch ya!

Top comments (0)