在 SpecFlow 步骤文件中使用依赖注入

Using dependency injection in SpecFlow step-file

我们使用 Unity 作为我们的依赖注入框架。

我想创建验收测试并需要一个 DossierService 实例。
不幸的是我得到以下异常:

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

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

    public DossierServiceSteps(IDossierService dossierService)
    {
        this.dossierService = dossierService;
    }
}

提前致谢

编辑: 我试过像这样使用 SpecFlow.Unity

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;
    }
}

UnityConfig 中,类型已正确注册

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

但我仍然遇到同样的异常。当我在 TestDependenciesCreateContainer() 方法的开头放置断点时,它不会中断...

我们通过实施 SpecFlow RuntimePlugin 解决了这个问题。在我们的例子中是 Castle.Windsor,但原则是一样的。首先定义覆盖默认 SpecFlow Instance Resolver 的插件:

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

CastleWindsorBindingInstanceResolver 中,我们需要实现单个方法:object ResolveBindingInstance(Type bindingType, IObjectContainer scenarioContainer);。此 class 包含容器和分辨率(在您的 IUnityContainer 实例中。我建议注入 self 的容器实例,以便您可以将 IUnityContainer 的实例注入 SpecFlow 绑定 classes)

这个插件需要在单独的程序集中,你将它加载到你的测试项目中,就像这样调整 app.config

<specFlow>
    <plugins>
      <add name="PluginAssemblyName" path="." type="Runtime" />
    </plugins>
...
</specFlow>

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

BoDI 是 Specflow 中提供的一个非常基本的依赖注入框架。你可以找到它的代码库here.

参见 this entry SpecFlow 的创建者 Gáspár Nagy(强调我的)的博客:

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 中找到如何注册类型和接口的示例 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>();
    }
}

但是,请注意(用 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.

对于在 Specflow 项目中寻找支持 DI 的可用 plugins/libraries 的任何人:https://docs.specflow.org/projects/specflow/en/latest/Extend/Available-Plugins.html#plugins-for-di-container

我更喜欢 - https://github.com/solidtoken/SpecFlow.DependencyInjection

例子

创建 DI 容器:

[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;
}

使用依赖项(通过参数化构造函数):

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

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

    ...
}

在这种情况下,通常您应该使用界面的 Mock