A Composition Root should be located near the point where user code first executes.

Prompted by a recent Internet discussion, my DIPPP co-author Steven van Deursen wrote to me in order to help clarify the Composition Root pattern.

In the email, Steven ponders whether it's defensible to use an API that looks like a Service Locator from within a unit test. He specifically calls out my article that describes the Auto-mocking Container design pattern.

In that article, I show how to use Castle Windsor's Resolve method from within a unit test:

[Fact]
public void SutIsController()
{
    var container = new WindsorContainer().Install(new ShopFixture());
    var sut = container.Resolve<BasketController>();
    Assert.IsAssignableFrom<IHttpController>(sut);
}

Is the test using a Service Locator? If so, why is that okay? If not, why isn't it a Service Locator?

This article argues that that this use of Resolve isn't a Service Locator.

Entry points defined #

The original article about the Composition Root pattern defines a Composition Root as the place where you compose your object graph(s). It repeatedly describes how this ought to happen in, or as close as possible to, the application's entry point. I believe that this definition is compatible with the pattern description given in our book.

I do realise, however, that we may never have explicitly defined what an entry point is.

In order to do so, it may be helpful to establish a bit of terminology. In the following, I'll use the terms user code as opposed to framework code.

Much of the code you write probably runs within some sort of framework. If you're writing a web application, you're probably using a web framework. If you're writing a message-based application, you might be using some message bus, or actor, framework. If you're writing an app for a mobile device, you're probably using some sort of framework for that, too.

Even as a programmer, you're a user of frameworks.

As I usually do, I'll use Tomas Petricek's distinction between libraries and frameworks. A library is a collection of APIs that you can call. A framework is a software system that calls your code.

User code running in a framework.

The reality is often more complex, as illustrated by the figure. While a framework will call your code, you can also invoke APIs afforded by the framework.

The point, however, is that user code is code that you write, while framework code is code that someone else wrote to develop the framework. The framework starts up first, and at some point in its lifetime, it calls your code.

Definition: The entry point is the user code that the framework calls first.

As an example, in ASP.NET Core, the (conventional) Startup class is the first user code that the framework calls. (If you follow Tomas Petricek's definition to the letter, ASP.NET Core isn't a framework, but a library, because you have to write a Main method and call WebHost.CreateDefaultBuilder(args).UseStartup<Startup>().Build().Run(). In reality, though, you're supposed to configure the application from your Startup class, making it the de facto entry point.)

Unit testing endpoints #

Most .NET-based unit testing packages are frameworks. There's typically little explicit configuration. Instead, you just write a method and adorn it with an attribute:

[Fact]
public async Task ReservationSucceeds()
{
    var repo = new FakeReservationsRepository();
    var sut = new ReservationsController(10, repo);
 
    var reservation = new Reservation(
        date: new DateTimeOffset(2018, 8, 13, 16, 53, 0, TimeSpan.FromHours(2)),
        email: "mark@example.com",
        name: "Mark Seemann",
        quantity: 4);
    var actual = await sut.Post(reservation);
 
    Assert.True(repo.Contains(reservation.Accept()));
    var expectedId = repo.GetId(reservation.Accept());
    var ok = Assert.IsAssignableFrom<OkActionResult>(actual);
    Assert.Equal(expectedId, ok.Value);
}
 
[Fact]
public async Task ReservationFails()
{
    var repo = new FakeReservationsRepository();
    var sut = new ReservationsController(10, repo);
 
    var reservation = new Reservation(
        date: new DateTimeOffset(2018, 8, 13, 16, 53, 0, TimeSpan.FromHours(2)),
        email: "mark@example.com",
        name: "Mark Seemann",
        quantity: 11);
    var actual = await sut.Post(reservation);
 
    Assert.False(reservation.IsAccepted);
    Assert.False(repo.Contains(reservation));
    Assert.IsAssignableFrom<InternalServerErrorActionResult>(actual);
}

With xUnit.net, the attribute is called [Fact], but the principle is the same in NUnit and MSTest, only that names are different.

Where's the entry point?

Each test is it's own entry point. The test is (typically) the first user code that the test runner executes. Furthermore, each test runs independently of any other.

For the sake of argument, you could write each test case in a new application, and run all your test applications in parallel. It would be impractical, but it oughtn't change the way you organise the tests. Each test method is, conceptually, a mini-application.

A test method is its own Composition Root; or, more generally, each test has its own Composition Root. In fact, xUnit.net has various extensibility points that enable you to hook into the framework before each test method executes. You can, for example, combine a [Theory] attribute with a custom AutoDataAttribute, or you can adorn your tests with a BeforeAfterTestAttribute. This doesn't change that the test runner will run each test case independently of all the other tests. Those pre-execution hooks play the same role as middleware in real applications.

You can, therefore, consider the Arrange phase the Composition Root for each test.

Thus, I don't consider the use of an Auto-mocking Container to be a Service Locator, since its role is to resolve object graphs at the entry point instead of locating services from arbitrary locations in the code base.

Summary #

A Composition Root is located at, or near, the entry point. An entry point is where user code is first executed by a framework. Each unit test method constitutes a separate, independent entry point. Therefore, it's consistent with these definitions to use an Auto-mocking Container in a unit test.



Wish to comment?

You can add a comment to this post by sending me a pull request. Alternatively, you can discuss this post on Twitter or somewhere else with a permalink. Ping me with the link, and I may respond.

Published

Monday, 17 June 2019 05:55:00 UTC

Tags



"Our team wholeheartedly endorses Mark. His expert service provides tremendous value."
Hire me!
Published: Monday, 17 June 2019 05:55:00 UTC