Do we need interfaces for unit testing?

Source: Jonathan Fulton

No we don’t need. It just doesn’t work like that and thinking this way can affect your project. So let’s imagine that you have a service that has some business logic and roles but in order to work, it must communicate with remote services and it also needs to use multiple databases. So, what will happen in the tests? The tests will require a long configuration of mocks and stubs. But why bother if there is a better way to do it. Firstly, what is most important for a good testing experience is the single responsibility principle, separation of concern, next is concentrating on writing the methods which have no side effects. Of course, also of importance is understanding basic patterns, which doesn’t mean “I know how to implement it”, it really means “I know which problem solves this pattern and why I use it”.

What about SOLID

Now, I guess, somebody will say, “We need to inject each service through an interface because it must have a valid SOLID. “No, it’s not true. The dependency inversion is about the management of the dependency direction. Robert C. Martin wrote in his book about the “naive implementation of DIP”, meaning that the heuristic approach of “each object needs an interface and DIP” isn’t a good idea because we can always find any objects that are non-volatile and concrete. He also wrote about SDP; the Stable Dependencies Principle, wherein he demonstrated how to use DIP to manage the dependency direction, and which direction is correct.

DIP and Architecture

We can find in other books patterns which are using dependency inversion in this way, for example, Decoupled Contract in SOA,

Contract Layer from SOA
Contract Layer from SOA

4 layers from Domain Driven Design, where we use a Repository interface in the domain layer to invert dependency of the infrastructure layer,

4 layers from DDD
4 layers from DDD

Abstract Core, Hexagonal Architecture and so on.

What I mean?

So why do I say all this? Because, using DIP for each “service”, “factories”, “parsers”, and so on can be redundant, and this can interferes with building a good architecture. Modern languages like C# also support other paradigms like functional programming. So, we can just use Func<T,TResult> or Action delegates to inject what we need without any interfaces, and, in many cases, it will be much better because when we use a delegate to inject, for example, any source of data like SQL database, we don’t need to use any isolation framework like Moq to create mock or stub, and an even better way is to strongly separate methods with side effects and methods without side effects whenever possible. Thanks to this, the tests are more readable and much easier to write.

Let me show it This is an example controller of ASP App. Of course, there is a lot of problems, like breaking MVC encapsulation, a legacy like TryUpdateModel, FormCollection, Repositories which are not doing what should do and so on. Let’s just take some ugly code to work on it.

public class PassengerController : Controller
{
    public ActionResult InformationAboutPassengers(FormCollection formCollection)
    {
        var viewModel = new PassengerInformationViewModel();

        if (TryUpdateModel(viewModel))
        {
            var confirmationEmail = emailService.GetConfirmationEmail(viewModel.Email);

            var passengersPositions = 
                new Regex(@">\p{Lu}{1}\p{Ll}+(\s{1}\p{Lu}{3,}(-\p{Lu}{3,})?)+<")
                .Matches(confirmationEmail)
                .Cast<Match>()
                .Select(m => m.Value);

            if (!passengersPositions.Any())
                throw new ApplicationException("No passengers positions.");

            var passengers = new Collection<string>();

            var vipPassengers = passengerRepository
                .QueryVipPassengersByPosition(passengersPositions);

            foreach (var passengersPosition in passengersPositions)
            {
                var matchPassengerValueByRegex = 
                    new Regex(@"(\s{1}\p{Lu}{3,}(-\p{Lu}{3,})?)+")
                    .Match(passengersPosition);

                if (!matchPassengerValueByRegex.Success)
                    throw new Exception("Error");

                var passenger = matchPassengerValueByRegex.Value.Substring(1);

                passengers.Add(passenger);
            }

            viewModel.Passengers = passengers
                .Where(p => p != string.Empty)
                .Distinct();

            viewModel.VipPassengers = vipPassengers.Distinct();

            return View(viewModel);
        }

        return RedirectToAction("Index");
    }
}

Now look on the test, long isn’t?

[Test]
public void Test()
{
    var autoMoq = new AutoMoqer();

    autoMoq.GetMock<IEmailService>()
        .Setup(x => x.GetConfirmationEmail(It.IsAny<string>()))
        .Returns("Email content");

    var callback = Enumerable.Empty<string>();
    autoMoq.GetMock<IPassengerRepository>()
	.Setup(x => x.QueryVipPassengersByPosition(It.IsAny<IEnumerable<string>>()))
        .Callback<IEnumerable<string>>(vipPassengers => callback = vipPassengers); 

    var form = new FormCollection();
    form.Add("Email", "AnyEmail");

    var response = autoMoq.Create<PassengerController>()
        .InformationAboutPassengers(form);

    var result = (ViewResult)response;
    var viewModel = (PassengerInformationViewModel)result.ViewData.Model;

    var expectedCallBack = new List<string>()
    {
        "any string",
        "any string"
    };

    var expectedPassengers = new List<string>
    {
        "any string",
        "any string"
    };

    CollectionAssert.AreEqual(callback, expectedCallBack);
    CollectionAssert.AreEqual(viewModel.Passengers, expectedPassengers);
}

First refactoring

All right, let’s get rid of all UI framework dependencies. Let’s cut off the Moq from tests.

...
public ActionResult InformationAboutPassengers(FormCollection formCollection)
{
    var viewModel = new PassengerInformationViewModel();

    if (TryUpdateModel(viewModel))
    {
        var newViewModel = this.passengersViewModelProvaider
            .PassengerInformationViewModel(viewModel.Email);

        return View(newViewModel);
    }

    return RedirectToAction("Index");
}
...

public class PassengerService
{
    public Result GetPassengerInformation(string confirmationEmail,
        Func<IEnumerable<string>, IEnumerable<string>> vipPassengersStrategy)
    {
        var passengersPositions = 
            new Regex(@">\p{Lu}{1}\p{Ll}+(\s{1}\p{Lu}{3,}(-\p{Lu}{3,})?)+<")
            .Matches(confirmationEmail)
            .Cast<Match>()
            .Select(m => m.Value);

        if (!passengersPositions.Any())
            throw new ApplicationException("No passengers positions.");

        var passengers = new Collection<string>();
        var vipPassengers = vipPassengersStrategy(passengersPositions);

        foreach (var passengersPosition in passengersPositions)
        {
            var matchPassengerValueByRegex = 
		new Regex(@"(\s{1}\p{Lu}{3,}(-\p{Lu}{3,})?)+")
                .Match(passengersPosition);

            if (!matchPassengerValueByRegex.Success)
                throw new Exception("Error");

            var passenger = matchPassengerValueByRegex.Value.Substring(1);

            passengers.Add(passenger);
        }

        var result = new Result();

        result.Passengers = passengers
            .Where(p => p != string.Empty)
            .Distinct();

        result.VipPassengers = vipPassengers.Distinct();

        return result;
    }
}

public class PassengersViewModelProvaider
{
    private readonly IEmailService emailService;
    private readonly IPassengerRepository passengerRepository;

    public PassengersViewModelProvaider(IEmailService emailService,
        IPassengerRepository passengerRepository)
    {
        this.emailService = emailService;
        this.passengerRepository = passengerRepository;
    }

    public PassengerInformationViewModel PassengerInformationViewModel(string email)
    {
        var viewModel = new PassengerInformationViewModel();
        var confirmationEmail = emailService.GetConfirmationEmail(viewModel.Email);

        var result = new PassengerService().GetPassengerInformation(confirmationEmail,
            Positions => passengerRepository.QueryVipPassengersByPosition(Positions));

        viewModel.Email = email;
        viewModel.Passengers = result.Passengers;
        viewModel.VipPassengers = result.VipPassengers;

        return viewModel;
    }
}

Now let’s look at what happened in the test, better isn’t?

[Test]
public void Test()
{
    var PassengerService = new PassengerService();

    var callback = Enumerable.Empty<string>();
    var result = PassengerService.GetPassengerInformation("email confirmation",
            vipPassengersStrategy: x => { callback = x; return null; });

    var expectedCallBack = new List<string>()
    {
        "any string",
        "any string"
    };

    var expectedPassengers = new List<string>
    {
        "any string",
        "any string"
    };

    CollectionAssert.AreEqual(callback, expectedCallBack);
    CollectionAssert.AreEqual(result.Passengers, expectedPassengers);
}

Second refactoring

Let’s correctly implemented the MVC pattern, remove “repository”, provide SRP and separate methods with side effects.

public class PassengerController : Controller
{
    private readonly PassengersViewModelProvaider passengersViewModelProvaider;

    public PassengerController(PassengersViewModelProvaider passengersViewModelProvaider)
    {
        this.passengersViewModelProvaider = passengersViewModelProvaider;
    }

    public ActionResult PassengerInformationViewModel(string email)
    {
        var newViewModel = passengersViewModelProvaider
        .GetPassengerInformationViewModel(email);

        if (newViewModel != null)
            return View(newViewModel);

        return RedirectToAction("Index");
    }
}

public class PassengersViewModelProvaider
{
    private readonly IEmailService emailService;
    private readonly IPassengerQueryService passengerQueryService;

    public PassengersViewModelProvaider(IEmailService emailService,
        IPassengerQueryService passengerQueryService)
    {
        this.emailService = emailService;
        this.passengerQueryService = passengerQueryService;
    }

    public PassengerInformationViewModel GetPassengerInformationViewModel(string email)
    {
        var confirmationEmail = emailService.GetConfirmationEmail(email);
        var Positions = ConfirmationEmailRewriter
        .RewritePassengersPositions(confirmationEmail);

        var vipPassengers = passengerQueryService.GetPassengers(Positions);
        var passengers = PassengersRewriter.RewritePassengers(Positions);

        return new PassengerInformationViewModel(passengers, vipPassengers);
    }
}

internal class ConfirmationEmailRewriter
{
    public static IEnumerable<string> RewritePassengersPositions(string confirmationEmail)
    {
        return new Regex(@">\p{Lu}{1}\p{Ll}+(\s{1}\p{Lu}{3,}(-\p{Lu}{3,})?)+<")
            .Matches(confirmationEmail)
            .Cast<Match>()
            .Where(x => x.Success)
            .Select(m => m.Value);
    }
}

internal class PassengersRewriter
{
    public static IEnumerable<string> RewritePassengers(
        IEnumerable<string> passengersPositions)
    {
        var regex = new Regex(@"(\s{1}\p{Lu}{3,}(-\p{Lu}{3,})?)+");

        return passengersPositions.Select(Position => regex.Match(Position))
            .Where(mached => mached.Success)
            .Where(Position => Position.Value.Length > 1)
            .Select(Position => Position.Value.Substring(1))
            .Where(Position => Position != string.Empty)
            .Distinct();
    }
}

Tests looks good, isn’t?

[Test]
public void Test()
{
    var passengerPositions = ConfirmationEmailRewriter
        .RewritePassengersPositions("confirmation");

    var expectedPassengerPositions = new List<string>()
    {
        "any string",
        "any string"
    };

    CollectionAssert.AreEqual(expectedPassengerPositions, passengerPositions);
}

[Test]
public void Test2()
{
    var anyPositions = Enumerable.Empty<string>();
    var passengers = PassengersRewriter.RewritePassengers(anyPositions);

    var expectedPassengers = new List<string>()
    {
        "any string",
        "any string"
    };

    CollectionAssert.AreEqual(expectedPassengers, passengers);
}

The DIP is still there, the facade PassengersViewModelProvaider has declared in interfaces what needs, but in the controller, I don’t use the DIP because it has no sense. The facade just manages other transaction scripts to solve any business problem. If you need a unit test you can just test the PassengersRewriter, but if you need an integration test you can make a test on the facade.

Conclusion

The dependency inversion principle isn’t about testing, it’s about the management of dependency direction. You don’t need interfaces for testing but it can be helpful. SRP, SOC and Separate methods without side effects are most important for a good testing experience. Mixing dependencies of UI framework with business logic is never a good idea.