SpecFlow creates and disposes the instances of your step definition classes automatically. It also allows injecting dependencies to these classes through a feature called context injection. This article shows how this injection can be configured to use the popular Autofac dependency injection framework.
Before jumping to the solution, let me give some explanation on the context. SpecFlow needs to take care of the step definition and other binding classes (ie. the classes with the [Binding]
attribute), it also configures the dependencies of the SpecFlow infrastructure classes. SpecFlow uses a special dependency injection framework called BoDi to handle these tasks. BoDi is an embeddable open-source mini DI framework available on GitHub. Although it is a generic purpose DI, its design and features are driven by the needs of SpecFlow.
By the time the SpecFlow project started, NuGet had not existed yet, so the libraries were distributed via zip downloads and the assemblies had to be referenced manually. Therefore we wanted to keep the SpecFlow runtime as a single-assembly library. For this, we needed a DI framework that was small and could be embedded as source code. Also we did not want to use a well-known DI framework, because it might have caused a conflict with the version of the framework used by your own project. This led me to create BoDi. BoDi does the job well for many cases, but from time to time there arises the question how someone can use another DI framework, the one that is used in the project to be tested, for example the popular Autofac framework.
But why is it important to use the same framework for runtime and testing? I will answer this question as well, but first let’s have a look at a concrete example.
Injecting runtime dependencies to step definitions
Let’s imagine that we are testing a calculator application. We have decided to automate the scenarios not through the UI but on one of the business layers of the application, typically on the controller layer that is just below the UI.
We have several step definitions that have to deal with the application. Initially when these step definitions are in the same binding class, we can just create a new instance of the CalculatorController
and store it in an instance field:
Later, as the automation layer is growing and the step definitions are split into multiple files, a ControllerContext
class can be introduced, which encapsulates the test state related to the controller. This context class can be injected to all binding classes using context injection wherever it may be necessary .
In this setup, whenever SpecFlow needs to invoke the first step definition from the CalculatorSteps
class, it creates an instance of it and since it depends on the ControllerContext
it also creates an instance of that one. This is all managed by SpecFlow using BoDi. The CalculatorController
, however, is created “manually” within the context class.
As the application is growing, the controller will also have more and more (implicit or explicit) dependencies. At some point, probably you will introduce a DI framework (let’s say Autofac) to manage these. So you will create a method that configures the application infrastructure and builds up a container object and you will configure your application to resolve the controller from the container instead of creating a new instance of it manually. To avoid duplication, you will probably also replace the instantiation of the controller in the context class to something that resolves the instance from a container, created for the scope of the scenario execution:
As you can see from this code, the test infrastructure could access the runtime container building logic. There is even a possibility to apply customization for it, e.g you can replace the data access classes with one that uses an in-memory database. So far, so good. This is typically how people use SpecFlow to automate applications that use DI.
Automating through multiple layers
For some applications, you might need to automate the application through multiple layers. Eg. you would like to run the scenarios against the domain layer for quick check but you want to have a nightly build when you run the same scenarios with UI automation. Also sometimes you want to automate the scenarios on the same layer, but with different configuration. For example, there are some scenarios that can work fine with a stub database (or other external dependency), there are some that need to perform the check together with the real database.
The concept of the context class in the example above might still work for a while. At the point marked with the “TODO” comment in the previous code snippet, you would probably add some “if” statements that build the container differently depending on some conditions (app.config setting, tags, etc.).
The complication starts when the context class is not just a simple container that wraps the state, but when it also contains automation logic. (I like to call these classes “driver” classes instead of context. The page object classes are also good examples of these, earlier they were called window drivers.) In this case, we would like to have different implementation of the driver classes and inject the right one to the step definition classes depending on the conditions mentioned earlier. If you would try to define an IControllerDriver
interface with two implementations and would try to inject the interface to the CalculatorSteps
class, you would get an exception, because SpecFlow (BoDi) cannot resolve interfaces by default.
Although it is possible to customize the dependency injection used by SpecFlow (see this gist as an example), it already scratches the boundaries of BoDi’s capacity. A better choice would be to use a more complex DI framework for this and let these configurations be managed together with the configuration of the runtime classes.
Customizing dependency injection in SpecFlow v2.1
In SpecFlow v2.1, we introduced a new infrastructure that is responsible for creating the instances of the binding classes. The default implementation works like before, it attempts to resolve these instances from a container created for each scenario execution. Now you can provide an alternative implementation, which uses for example Autofac . You have to write a simple SpecFlow runtime plugin that configures SpecFlow to use your implementation. You have to provide an implementation of the IBindingInstanceResolver
interface in the plugin. The interface defines a single method called ResolveBindingInstance
. This method receives the instance of the BoDi container for the scenario execution. This can be used to store the container of your own DI framework, in order to ensure that the lifetime scope of the binding instances is limited to the scenario execution.
I have created a reusable plugin for using Autofac, which is available on NuGet or as source code on GitHub. You can use this plugin as an example, or just use it as it is from NuGet.
Using the SpecFlow.Autofac plugin
If you want to use the plugin from NuGet, you need to add a reference to the SpecFlow.Autofac package from NuGet:
PM> Install-Package SpecFlow.Autofac
The NuGet package will add the necessary assembly to the project and configure the plugin for SpecFlow in the app.config
. The next step is to provide a static method that returns the Autofac container builder that represents the configuration of your dependencies. This method has to be marked with the [ScenarioDependencies]
attribute (the attribute is also defined by the plugin). By replacing the default registration, it is now your responsibility to properly configure the steps classes and all other classes you would like to use for automation (BoDi can resolve types without configuration). You can do this by either auto-register all public classes from the SpecFlow project assembly or only those that are marked with the [Binding]
attribute. You can see an example for both options in the snippet below.
Create your own
You can also use the SpecFlow.Autofac project as an example and create a similar plugin for your specific circumstances or for another DI framework. (Please let us know about it, so that we can share with the other SpecFlow users.)
Happy injecting!
8 thoughts on “SpecFlow tips: Customizing dependency injection with Autofac”