-
Notifications
You must be signed in to change notification settings - Fork 337
Internal Architecture
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.