Skip to content

Internal Architecture

Blair Conrad edited this page Mar 5, 2018 · 7 revisions

AutoFixture is built around a very extensible kernel. Elements of the kernel can be composed of a wide variety of granular components to provide arbitrary object creation services, but the default specialization is the Fixture class, which what you typically use to Create auto-generated values (aka Specimens, Anonymous Variables):

var fixture = new Fixture();
var value = fixture.Create<string>();

The Fixture class is defined in the AutoFixture namespace, and unless you explicitly need to extend the kernel, you should only need to import this namespace. The kernel, on the other hand, is implemented in the AutoFixture.Kernel namespace. The Fixture class implements the appropriate kernel interface that enables it to create Specimens.

Fixture high-level layout

The Fixture class is a specialization that packages the components of the kernel in a very specific way to implement the features and behavior of AutoFixture as shown on this illustration:

When you create a Fixture instance using the default constructor it prepares a set of Specimen Builders called the engine parts, corresponding to the middle block of the illustration. These Specimen Builders contain logic that handles well-known primitive types such as integers and strings, as well as a set of components that use Reflection to instantiate complex types via their constructors.

Customizations

In a default Fixture, the Customizations collection is empty, but we can use it to customize the Fixture instance by adding ISpecimenBuilder instances. Since the kernel uses the first specimen created by an ISpecimenBuilder, adding a Specimen Builder to the Customization collection intercepts the default engine parts, allowing the customization to get a shot at each request before the default engine parts handle it.

The Customize<T> method of Fixture itself uses this structure to customize the instance.

Residue Collectors

There are requests that a Fixture instance cannot satisfy. As an example, a request for an interface type cannot be satisfied because an interface has no public constructors. Adding an ISpecimenBuilder instance to the ResidueCollectors collection gives that Specimen Builder a chance to handle such residue before Fixture throws an exception. The optional AutoMoq extension, for instance, is implemented via a custom Residue Collector that handle requests for interfaces by returning a mock of that interface.

Behaviors

A Behavior is simply a Decorator that can monitor or intercept requests and specimens going in and out of the Fixture instance. Tracing and Recursion Guarding are implemented as Behaviors.

Specimen Building

This section describes how the kernel creates specimens when you invoke the Create<T> or CreateMany<T> methods. It also describes some important interfaces that serve as the key extensibility points for the kernel.

AutoFixture's kernel creates objects through a set of interfaces and an engine that mainly uses the Chain of Responsibility pattern. From a high-level perspective, we request a specimen from the kernel, and it returns an instance if any participant from the Chain of Responsibility can satisfy that request.

Requests

To request a specimen we invoke the Create method from the ISpecimenBuilder interface:

public interface ISpecimenBuilder
{
    object Create(object request, ISpecimenContext context);
}

So what is a request? Technically, it can be any object, but in the typical scenario it's a Type instance. When we invoke the Create<T> or CreateMany<T> extension methods, they eventually end up invoking the Create method with typeof(T) as the request. However, it should be pointed out that although this is the typical scenario, it may result in lots of subsidiary requests for other instances, such as PropertyInfo, ParameterInfo or completely custom types. As long as at least one ISpecimenBuilder can handle the request, the request is considered valid.

Handling Requests

The AutoFixture kernel is built around a chain of ISpecimenBuilder instances, typically encapsulated in a CompositeSpecimenBuilder. When we invoke the Create method, a CompositeSpecimenBuilder will invoke the Create method of all its contained Builders until one of them provides a specimen. At this point, the request is considered to be satisfied and the rest of the builders are ignored. The created specimen is returned to the caller.

It is important to note that null is considered a valid specimen, so to signal that it cannot satisfy a request, a Specimen Builder must return an instance of the NoSpecimen signal type:

return new NoSpecimen();

Any other returned value (including null) is considered a valid specimen, which terminates the Chain of Responsibility and returns the specimen.

The Specimen Context

When requesting a specimen we must also provide an ISpecimenContext instance. This is the context in which the request is handled:

public interface ISpecimenContext
{
    object Resolve(object request);
}

The Resolve method simply provides a mechanism whereby a Specimen Builder can create subsidiary specimens to satisfy a request. As an example, the ConstructorInvoker Specimen Builder satisfies requests for types by using Reflection to invoke a public constructor on a Type. If that constructor requires parameters, ConstructorInvoker obtains the specimens involved via requests against the context based on the corresponding ParameterInfo instances.

For each context.Resolve invocation, the SpecimenBuilder Chain of Responsibility is recursively invoked with the new request.

One of the concepts that most extenders seem to have a hard time grasping is the concept of a Relay. This is an ISpecimenBuilder that handles a request by issuing a new (different) request to the context and then wrapping the result of that operation as a new specimen. One example is the ArrayRelay, which handles requests for arrays by issuing a new request for multiple specimens of the array type and returning the sequence as an array. If you ever feel the desire to use Activator.CreateInstance to create specimens you should create a Relay instead.

Extending the Kernel

To extend the kernel, ISpecimenBuilder is the main interface to implement. Since the first specimen yielded by a Specimen Builder is the specimen returned, the kernel can be customized by inserting specialized ISpecimenBuilder implementations early in the Chain of Responsibility.