首页 > 解决方案 > 在 SpecFlow 3 生命周期挂钩中使用 SimpleInjector 范围

问题描述

我正在尝试在 .Net Core 2.1 Web API 中设置我的 Specflow (V. 3.0.155 beta) 测试,并且我正在遵循我在以前从事的一些 .Net Framework Web API 项目中使用过的结构这样做。

但是当我尝试解决我的依赖关系时遇到了麻烦,似乎我正在超出范围,所以当我尝试解决某些问题时,即我的测试数据上下文,我从 SpecFlow 收到以下错误:

消息:SimpleInjector.ActivationException:类型 ProjectNexusContext 的注册委托引发了异常。ProjectNexusContext 注册为“异步范围”生活方式,但在活动(异步范围)范围的上下文之外请求实例。----> SimpleInjector.ActivationException:ProjectNexusContext 注册为“异步范围”生活方式,但在活动(异步范围)范围的上下文之外请求实例。

堆栈跟踪:

结果 StackTrace:在 SimpleInjector.InstanceProducer.GetInstance()
在 SimpleInjector.Container.GetInstanceTService at ProjectNexus.ApplicationContext.ResolveT 在 C:\Users\dawes\source\repos\ProjectNexus\ProjectNexus\ApplicationContext.cs:line 18 at ProjectNexus.Engine.Specs.LifecycleTestHooks.AfterStep() 在 C:\ Users\dawes\source\repos\ProjectNexus\ProjectNexus.Engine.Specs\LifecycleTestHooks.cs:TechTalk.SpecFlow.Bindings.BindingInvoker.InvokeBinding 的第 37 行(IBinding 绑定,IContextManager contextManager,Object[] 参数,ITestTracer testTracer,TimeSpan& 持续时间)在 D:\a\1\s\TechTalk.SpecFlow\Bindings\BindingInvoker.cs: 第 73 行 TechTalk.SpecFlow.Infrastructure.TestExecutionEngine.InvokeHook(IBindingInvoker 调用程序,IHookBinding hookBinding,HookType hookType) 在 D:\a\1\ s\TechTalk.SpecFlow\Infrastructure\TestExecutionEngine.cs:TechTalk 的第 246 行。SpecFlow.Infrastructure.TestExecutionEngine.FireEvents(HookType hookType) 在 D:\a\1\s\TechTalk.SpecFlow\Infrastructure\TestExecutionEngine.cs:TechTalk.SpecFlow.Infrastructure.TestExecutionEngine.ExecuteStep(IContextManager contextManager, StepInstance stepInstance) 的第 232 行在 D:\a\1\s\TechTalk.SpecFlow\Infrastructure\TestExecutionEngine.cs:第 367 行 TechTalk.SpecFlow.Infrastructure.TestExecutionEngine.Step(StepDefinitionKeyword stepDefinitionKeyword, String keyword, String text, String multilineTextArg, Table tableArg) 在 D :\a\1\s\TechTalk.SpecFlow\Infrastructure\TestExecutionEngine.cs:D:\a\1\s 中 TechTalk.SpecFlow.TestRunner.Given(String text, String multilineTextArg, Table tableArg, String keyword) 的第 475 行\TechTalk.SpecFlow\TestRunner.cs:ProjectNexus.Engine.Specs.Tests.Client 的第 75 行。Authentication.LogInFeature.FeatureBackground() 在 C:\Users\dawes\source\repos\ProjectNexus\ProjectNexus.Engine.Specs\Tests\Client\Authentication\Log In.feature:line 7 在 ProjectNexus.Engine.Specs.Tests.Client .Authentication.LogInFeature.LogInWithAValidUsernameAndPassword() 在 C:\Users\dawes\source\repos\ProjectNexus\ProjectNexus.Engine.Specs\Tests\Client\Authentication\Log In.feature:line 6 --ActivationException at SimpleInjector.Scope.GetScopelessInstance [Timplementation](ScopedRegistrationEngine.Specs\Tests\Client\Authentication\Log In.feature:line 6 --ActivationException at SimpleInjector.Scope.GetScopelessInstance[TImplementation](ScopedRegistrationEngine.Specs\Tests\Client\Authentication\Log In.feature:line 6 --ActivationException at SimpleInjector.Scope.GetScopelessInstance[TImplementation](ScopedRegistration1 registration) at SimpleInjector.Advanced.Internal.LazyScopedRegistration1.GetInstance(Scope scope) at lambda_method(Closure) at SimpleInjector.InstanceProducer.GetInstance() 结果消息:SimpleInjector.ActivationException:ProjectNexusContext 类型的注册委托引发了异常。ProjectNexusContext 注册为“异步范围”生活方式,但在活动(异步范围)范围的上下文之外请求实例。----> SimpleInjector.ActivationException:ProjectNexusContext 注册为“异步范围”生活方式,但在活动(异步范围)范围的上下文之外请求实例。结果标准输出:给定以下用户存储在数据库中---表步骤参数---| 福内姆 | 姓氏 | 用户名 | | 约翰 | 史密斯 | js001 | -> 错误:ProjectNexusContext 类型的注册委托引发了异常。ProjectNexusContext 注册为“异步范围”生活方式,但在活动(异步范围)范围的上下文之外请求实例。

我正在使用 Simple Injector 4.4.2,在我的测试项目中,我设置了我的容器并将我的实例注册到一个静态类方法中,该方法在 BeforeTestRun SpecFlow 生命周期挂钩中调用。

然后在我的 BeforeScenario 钩子中,我在所述容器上开始一个新的 AsyncScopedLifestyle 范围,该范围将在给定场景中始终使用。接下来,在我的 AfterScenario 钩子中,我处理了这种生活方式。我查看了有关这种生活方式的 SimpleInjector 文档,并且知道在活动范围的上下文之外时会引发此异常,但我不明白为什么我在活动上下文之外!

在检查 BeforeStep 挂钩中的 Scope 时,我可以看到,尽管没有释放,但 ScopeManager 中的 CurrentScope 属性为空,因此我显然不在活动范围上下文中。

这在以前从未成为问题,并且代码与前面提到的在他们的测试项目中以完全相同的方式使用 SimpleInjector 的项目中的代码完全相同。我什至逐步检查了它们并检查了 BeforeStep 钩子中的范围,而 ScopeManager 中的 CurrentScope 是范围,所以它们显然没有超出范围。

我希望有人可能会看到我遗漏的东西,或者对如何解决这个问题有一些建议。

我在下面包含了我的 SpecFlow 挂钩和 IoC 设置的代码:

SpecFlow 挂钩:

[Binding]
public class LifecycleTestHooks
{

[BeforeTestRun]
public static void BeforeTestRun()
{
    TestIocConfiguration.Configure();
}

[BeforeScenario]
public void BeforeScenario()
{
    TestIocConfiguration.StartExecutionScope();
}

[BeforeStep]
public void BeforeStep()
{
    var projectDbContext = ApplicationContext.Resolve<ProjectNexusContext>();
    projectDbContext.Database.BeginTransaction();
}

[AfterStep]
public void AfterStep()
{
    var projectDbContext = ApplicationContext.Resolve<ProjectNexusContext>();
    if (projectDbContext.Database.CurrentTransaction != null)
    {
        try
        {
            projectDbContext.Database.CommitTransaction();
        }
        catch (Exception)
        {
            projectDbContext.Database.RollbackTransaction();
            throw;
        }
    }
    TestIocConfiguration.CheckExecutionScope();
}

[AfterScenario]
public void AfterScenario()
{
    TestIocConfiguration.EndExecutionScope();
}
}

IoC 设置

 public class TestIocConfiguration
    {
        static Container container;

        public static void Configure()
        {
            container = ApplicationContext.Container;
            container.Options.DefaultScopedLifestyle = new AsyncScopedLifestyle();

            EngineInitialisation.Initialise();

            container.Options.AllowOverridingRegistrations = true;

            RegisterTestDatabaseContext();
            RegisterTestApplicationConfiguration();
            RegisterTestLdapConnectionService();
            RegisterTestContext();
        }

        private static void RegisterTestDatabaseContext()
        {
            var testContext =
                new ProjectNexusContext(new TestContextHelper().GetDbContextOptionsBuilder());
            testContext.Database.OpenConnection();
            testContext.Database.EnsureCreated();
            container.Register<ProjectNexusContext>(() => testContext, Lifestyle.Scoped);
        }

        private static void RegisterTestLdapConnectionService()
        {
            container.Register<ILdapConnectionService, TestLdapConnectionService>(Lifestyle.Scoped);
        }

        private static void RegisterTestApplicationConfiguration()
        {
            var appConfig = new ApplicationConfiguration
            {
                LdapHost = "",
                LdapPort = 0,
                ApplicationSecret = "TestSecret",
                TokenExpirationDays = 1
            };
            container.Register<ApplicationConfiguration>(() => appConfig, Lifestyle.Scoped);
        }

        private static void RegisterTestContext()
        {
            container.Register<TestContext>(Lifestyle.Singleton);
        }

        public static Scope StartExecutionScope()
        {
            return AsyncScopedLifestyle.BeginScope(container);
        }

        public static void EndExecutionScope()
        {
            Lifestyle.Scoped.GetCurrentScope(container)?.Dispose();
        }
    }

应用程序上下文

public class ApplicationContext
{
    public static readonly Container Container;
    public static readonly Mapper Mapper;

    static ApplicationContext()
    {
        Container = new Container();
    }

    public static T Resolve<T>() where T : class
    {
        return Container.GetInstance<T>();
    }

    public static Cast Resolve<T, Cast>()
        where Cast : T
        where T : class
    {
        return (Cast)Container.GetInstance<T>();
    }
}

标签: c#asp.net-corespecflowasp.net-core-2.1simple-injector

解决方案


在每个规范之前,您需要创建一个新的 Container 来使用,因为我认为重复使用不会达到您对当前设置的期望。

您可以添加一个SetContainer在您ApplicationContext喜欢的方法中调用的方法:

public static void SetContainer(Container container)
{
    Container = container;
}

然后在你的BeforeScenario钩子中你可以运行你的 IoC 配置,但是新建一个容器并使用你的SetContainer方法

更新:似乎在 Specflow 3-beta 中,活动范围一旦离开测试挂钩方法(例如 BeforeScenario)就会被释放

现在,您可以使用单例生活方式注册您的实例,并为每个测试使用一个新容器(就像您可以使用我上面提出的建议一样)。

我在 Specflow 2.3.2 中尝试了相同的代码,它工作正常,但在 Specflow 3-beta 中中断。


推荐阅读