简体   繁体   中英

(How) Can Specflow test cases be run with Autofac as the IoC container?

I'm trying to adopt BDD in my organization and since C#.Net is our primary mode of development, Specflow is our best bet for "anything Cucumber".

However, I've been a Spring aficionado in the past, but at my company we're using Autofac for various parts of the application. However, I'm unable to find any resources that explain "how" Autofac can be used to "trigger" Specflow's BDD tests and provide the necessary wiring of dependencies.

I plan to have Autofac be responsible for instantiating, wiring and executing everything instead of executing Specflow and have methods calling Autofac littered everywhere ie, using Autofac as a Service Locator instead of a DI/IoC Container. Can this even be done or am I looking at it the wrong way and there is a better way of achieving the same goal? Or should I purely rely on Specflow's "internal container" for DI and forget about Autofac altogether?

Possible approaches:

  1. Have Autofac instantiate and wire everything and run Specflow tests (not sure if this is even possible/recommended).
  2. Have Specflow globally instantiate Autofac which is wired with the necessary dependencies for the remainder of the code. Possible that step definitions may land up using Autofac as a factory to get what they need.

Pros/Cons:

  1. The first approach is ideal since it prevents any dependencies from Specflow to Autofac. The former is oblivious of the latter. Completely transparent. Preferred, but not sure how to go about it.
  2. The latter approach "could" work if Autofac could be globally instantiated by Specflow once for use later. But this would lead to lots of calls to Autofac from within the step definitions coupling the two libraries together. Not preferable.

I'm not sure how to achieve either of them and if it's better to let Specflow handle DI and forget about Autofac or have Autofac fire everything up or if there is some middle ground?

Current BDD setup: Specflow, Selenium/PhantomJS, Xunit. Looking to combine with Autofac.

I plan to have Autofac be responsible for instantiating, wiring and executing everything instead of executing Specflow and have methods calling Autofac littered everywhere.

  • I don't really see how you would achieve this and still be able to run your tests from Visual Studio or ReSharper (I'm assuming you don't want to lose that).

Have Specflow globally instantiate Autofac which is wired with the necessary dependencies for the remainder of the code. Possible that step definitions may land up using Autofac as a factory to get what they need.

  • That is exactly what I am doing and what I recommend.

But this would lead to lots of calls to Autofac from within the step definitions coupling the two libraries together.

  • I wouldn't recommend resolving the types you need in your Step Definition classes by calling AutoFac 's methods directly. I would create a method in a base class or bind an object to specflow's mini DI Container that has said method.

What I would do is create a service locator interface named IServiceLocator (yes, I know that service locator is an antipattern )

public interface IServiceLocator
{
    T Get<T>();
}

We would then create an implementation of this interface using Autofac (keep in mind that you could replace this for another implementation)

public class AutoFacServiceLocator: IServiceLocator
{
    private readonly IContainer _container;

    public AutoFacServiceLocator(IContainer container)
    {
        _container = container;
    }

    public T Get<T>()
    {
        //Here you add your resolution logic
    }
}

The fact that for each scenario we need an instance of IServiceLocator , we want to be able to get it through Specflow 's Context Injection .

[Binding]
public class Hooks
{
    private readonly IObjectContainer _objectContainer;

    public Hooks(IObjectContainer objectContainer)
    {
        _objectContainer = objectContainer;
    }

    [BeforeScenario]
    public void RegisterServiceLocator()
    {
        var container = CreateContainer();

        var serviceLocator = new AutoFacServiceLocator(container);

        _objectContainer.RegisterInstanceAs<IServiceLocator>(serviceLocator);
    }

    private IContainer CreateContainer() { /*Create your container*/}
}

Finally here's the usage

[Binding]
public class Steps
{
    private readonly IServiceLocator _serviceLocator;

    public Steps(IServiceLocator serviceLocator)
    {
        _serviceLocator = serviceLocator;
    }

    [Given(@"I have entered (.*) into the calculator")]
    public void GivenIHaveEnteredIntoTheCalculator(int p0)
    {
        Foo foo = _serviceLocator.Get<Foo>();
    }
}

Update:

travis-illig said in the comments below

If you go with service location, try CommonServiceLocator rather than creating your own interface

I don't really think there would be the need to use CommonServiceLocator . The calls to your service locator should match the constructor injections in your Controllers, which means that those calls should be covered by the method Get<T>() .

Quoting ctavares , one of CommonServiceLocator 's developers

Should I use this library for my applications?

Typically, the answer to this question is no. Once you've decided on a container that suits your project, there's not a whole lot of benefit from writing your whole application in a way that can switch containers. For libraries that must fit into other ecosystems and play nicely with other libraries, it's an important feature, but for applications the extra layer of abstraction really doesn't buy you much.

CommonServiceLocator is meant for libraries and frameworks, since PhD 's test project is his and his team's I wouldn't recommend introducing more dependencies.

As of SpecFlow v2.1, there is now the ability to integrate different IoC containers for dependency injection.

There is a SpecFlow.Autofac package for this already, created by Gaspar Nagy: https://github.com/gasparnagy/SpecFlow.Autofac .

Gaspar provides information on how to use this package (or to write one for a different IoC container): http://gasparnagy.com/2016/08/specflow-tips-customizing-dependency-injection-with-autofac/

In the case of Autofac, the NuGet package does the hard work for you, and you only need to provide the details of how to create the container builder:

public static class TestDependencies
{
    [ScenarioDependencies]
    public static ContainerBuilder CreateContainerBuilder()
    {
        // create container with the runtime dependencies
        var builder = Dependencies.CreateContainerBuilder();

        //TODO: add customizations, stubs required for testing

        //auto-reg all types from our assembly
        //builder.RegisterAssemblyTypes(typeof(TestDependencies).Assembly).SingleInstance();

        //auto-reg all [Binding] types from our assembly
        builder.RegisterTypes(typeof(TestDependencies).Assembly.GetTypes().Where(t => Attribute.IsDefined(t, typeof(BindingAttribute))).ToArray()).SingleInstance();

        return builder;
    }
}

(In this snippet above, Dependencies.CreateContainerBuilder() is reusing the builder from the application and them supplementing it with registrations for the testing environment. See https://github.com/gasparnagy/SpecFlow.Autofac/tree/master/sample/MyCalculator for more detail.)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM