Using dependency injection in SpecFlow step-file
Asked Answered
R

4

7

We're using Unity as our dependency injection framework.

I want to create an acceptance test and need an instance of DossierService.
Unfortunately I get the following exception:

BoDi.ObjectContainerException: 'Interface cannot be resolved [...]'

[Binding]
public class DossierServiceSteps : BaseSteps
{
    private IDossierService dossierService;

    public DossierServiceSteps(IDossierService dossierService)
    {
        this.dossierService = dossierService;
    }
}
  • What exactly is BoDi? I can't find any useful information..
  • How can I tell SpecFlow to use the normal Unity container?

Thanks in advance

Edit: I've tried using SpecFlow.Unity like so:

public static class TestDependencies
{
    [ScenarioDependencies]
    public static IUnityContainer CreateContainer()
    {
        var container = UnityConfig.GetConfiguredContainer();

        container.RegisterTypes(typeof(TestDependencies).Assembly.GetTypes().Where(t => Attribute.IsDefined(t, typeof(BindingAttribute))),
            WithMappings.FromMatchingInterface,
            WithName.Default,
            WithLifetime.ContainerControlled);

        return container;
    }
}

In UnityConfig the types are correctly registered

container.RegisterType<IDossierService, DossierService>(new InjectionConstructor(typeof(IDataService), typeof(IDossierRepository), typeof(IDbContext), true));

But I still get the same exception. When I put a breakpoint at the start of the CreateContainer() method of TestDependencies it doesn't break...

Richelieu answered 22/9, 2017 at 6:22 Comment(0)
T
2

We solved this problem by implementing SpecFlow RuntimePlugin. In our case it was Castle.Windsor, but principle is the same. First you define the plugin which override default SpecFlow Instance Resolver:

public class CastleWindsorPlugin : IRuntimePlugin
{
    public void Initialize(RuntimePluginEvents runtimePluginEvents, RuntimePluginParameters runtimePluginParameters)
    {
        runtimePluginEvents.CustomizeScenarioDependencies += (sender, args) =>
        {
            args.ObjectContainer.RegisterTypeAs<CastleWindsorBindingInstanceResolver, IBindingInstanceResolver>();
        };
    }
}

Where in CastleWindsorBindingInstanceResolver we needed to implement single method: object ResolveBindingInstance(Type bindingType, IObjectContainer scenarioContainer);. This class contains container and resolution (in your case instance of IUnityContainer. I recommend to inject to the container instance of self, so that you could inject the instance of IUnityContainer to SpecFlow binding classes)

This plugin needs to be in separate assembly and you load that to your test project like adjusting app.config like this:

<specFlow>
    <plugins>
      <add name="PluginAssemblyName" path="." type="Runtime" />
    </plugins>
...
</specFlow>
Torin answered 26/9, 2017 at 14:43 Comment(0)
D
6

For anyone looking for available plugins/libraries that support DI in Specflow project: https://docs.specflow.org/projects/specflow/en/latest/Extend/Available-Plugins.html#plugins-for-di-container

I prefer - https://github.com/solidtoken/SpecFlow.DependencyInjection

Example

Create DI container:

[ScenarioDependencies]
public static IServiceCollection CreateServices()
{
    var services = new ServiceCollection();

    Config config = JObject.Parse(File.ReadAllText("config.json")).ToObject<Config>();
    
    services.AddSingleton(config);
    services.AddScoped<DbConnections>();
    services.AddScoped<ApiClients>();

    return services;
}

Consume dependencies (via parameterized constructors):

[Binding]
public sealed class CalculatorStepDefinitions
{
    private readonly DbConnections dbConnections;

    public CalculatorStepDefinitions(DbConnections dbConnections) => this.dbConnections = dbConnections;

    ...
}
Decare answered 2/3, 2022 at 7:46 Comment(1)
getting this error-->BoDi.ObjectContainerException : Concurrent object resolution timeout (potential circular dependency).Bellbird
T
2

We solved this problem by implementing SpecFlow RuntimePlugin. In our case it was Castle.Windsor, but principle is the same. First you define the plugin which override default SpecFlow Instance Resolver:

public class CastleWindsorPlugin : IRuntimePlugin
{
    public void Initialize(RuntimePluginEvents runtimePluginEvents, RuntimePluginParameters runtimePluginParameters)
    {
        runtimePluginEvents.CustomizeScenarioDependencies += (sender, args) =>
        {
            args.ObjectContainer.RegisterTypeAs<CastleWindsorBindingInstanceResolver, IBindingInstanceResolver>();
        };
    }
}

Where in CastleWindsorBindingInstanceResolver we needed to implement single method: object ResolveBindingInstance(Type bindingType, IObjectContainer scenarioContainer);. This class contains container and resolution (in your case instance of IUnityContainer. I recommend to inject to the container instance of self, so that you could inject the instance of IUnityContainer to SpecFlow binding classes)

This plugin needs to be in separate assembly and you load that to your test project like adjusting app.config like this:

<specFlow>
    <plugins>
      <add name="PluginAssemblyName" path="." type="Runtime" />
    </plugins>
...
</specFlow>
Torin answered 26/9, 2017 at 14:43 Comment(0)
L
2

What exactly is BoDi? I can't find any useful information..

BoDI is a very basic Dependency Injection framework that ships within Specflow. You can find its code repository here.

See this entry from the blog of SpecFlow's creator, Gáspár Nagy (emphasis mine):

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.

You can find an example of how to register types and interfaces in BoDI here:

[Binding]
public class DependencyConfiguration
{
    private IObjectContainer objectContainer;

    public DependencyConfiguration(IObjectContainer objectContainer)
    {
        this.objectContainer = objectContainer;
    }

    [BeforeScenario(Order = 0)]
    public void ConfigureDependencies()
    {
        if (...)
            objectContainer.RegisterTypeAs<RealDbDriver, IControllerDriver>();
        else
            objectContainer.RegisterTypeAs<StubDbDriver, IControllerDriver>();
    }
}

However, be warned that (in the words of Gáspár Nagy):

Although it is possible to customize the dependency injection used by SpecFlow, it already scratches the boundaries of BoDi’s capacity. A better choice would be to use a more complex DI framework for this.

Ladybird answered 7/6, 2021 at 22:23 Comment(1)
That didn't worked for me at all... I went with @Decare answer nor using constructor injection, neither using getting the objectContainer and then calling Resolve<IInterface>()...Supervision
M
0

In this situation usually you should use Mock of your Interface.

Majewski answered 9/4, 2022 at 5:16 Comment(1)
wow that's an old post that you found ;-) Back then I didn't want a Mock since it was an acceptance test and not a unit test.Richelieu

© 2022 - 2024 — McMap. All rights reserved.