3

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...

xeraphim
  • 4,375
  • 9
  • 54
  • 102

4 Answers4

4

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;

    ...
}
Meer
  • 678
  • 6
  • 12
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>
Tomas Panik
  • 4,337
  • 2
  • 22
  • 31
1

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.

jarmanso7
  • 117
  • 1
  • 1
  • 8
0

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

akbar
  • 625
  • 6
  • 12
  • 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. – xeraphim Apr 11 '22 at 08:02