在SpecFlow 3生命周期挂钩中使用SimpleInjector范围

时间:2018-12-20 17:33:58

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

我正在尝试在.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在   中的ProjectNexus.ApplicationContext.ResolveT   C:\ Users \ dawes \ source \ repos \ ProjectNexus \ ProjectNexus \ ApplicationContext.cs:line   18在ProjectNexus.Engine.Specs.LifecycleTestHooks.AfterStep()中   C:\ Users \ dawes \ source \ repos \ ProjectNexus \ ProjectNexus.Engine.Specs \ LifecycleTestHooks.cs:line   37岁   TechTalk.SpecFlow.Bindings.BindingInvoker.InvokeBinding(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:line   246点   TechTalk.SpecFlow.Infrastructure.TestExecutionEngine.FireEvents(HookType   hookType)中   D:\ a \ 1 \ s \ TechTalk.SpecFlow \ Infrastructure \ TestExecutionEngine.cs:line   232点   TechTalk.SpecFlow.Infrastructure.TestExecutionEngine.ExecuteStep(IContextManager   contextManager,StepInstance stepInstance)   D:\ a \ 1 \ s \ TechTalk.SpecFlow \ Infrastructure \ TestExecutionEngine.cs:line   367点   TechTalk.SpecFlow.Infrastructure.TestExecutionEngine.Step(StepDefinitionKeyword   stepDefinitionKeyword,String关键字,String文本,String   multilineTextArg,Table tableArg)在   D:\ a \ 1 \ s \ TechTalk.SpecFlow \ Infrastructure \ TestExecutionEngine.cs:line   475在TechTalk.SpecFlow.TestRunner.Given(String text,String   multilineTextArg,Table tableArg,String关键字)   D:\ a \ 1 \ s \ TechTalk.SpecFlow \ TestRunner.cs:第75行位于   ProjectNexus.Engine.Specs.Tests.Client.Authentication.LogInFeature.FeatureBackground()   在   C:\ Users \ dawes \ source \ repos \ ProjectNexus \ ProjectNexus.Engine.Specs \ Tests \ Client \ Authentication \ Log   功能:第7行   ProjectNexus.Engine.Specs.Tests.Client.Authentication.LogInFeature.LogInWithAValidUsernameAndPassword()   在   C:\ Users \ dawes \ source \ repos \ ProjectNexus \ ProjectNexus.Engine.Specs \ Tests \ Client \ Authentication \ Log   功能:第6行   --SimpleInjector.Scope.GetScopelessInstance [TImplementation](ScopedRegistration 1 registration) at SimpleInjector.Advanced.Internal.LazyScopedRegistration 1.GetInstance(Scope   范围)在lambda_method(Closure)在   SimpleInjector.InstanceProducer.GetInstance()结果消息:   SimpleInjector.ActivationException:类型的注册委托   ProjectNexusContext引发了异常。 ProjectNexusContext是   已注册为“异步作用域”生活方式,但需要实例   在有效(异步作用域)范围的上下文之外。 ---->   SimpleInjector.ActivationException:ProjectNexusContext是   已注册为“异步作用域”生活方式,但需要实例   在有效(异步作用域)范围的上下文之外。结果   StandardOutput:给定以下用户存储在数据库中   ---表步参数--- |前言|姓|用户名| |约翰|史密斯| js001 |   ->错误:ProjectNexusContext类型的注册委托引发了异常。 ProjectNexusContext已注册为“异步作用域”   生活方式,但实例是在   活动(异步作用域)作用域。

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

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

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

这以前从来都不是问题,并且代码与上述先前的项目完全相同,后者以完全相同的方式在其测试项目中使用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>();
    }
}

1 个答案:

答案 0 :(得分:1)

在每个规格之前,您需要创建一个新的容器才能使用,因为我认为重复使用不会满足您当前设置的期望。

您可以在SetContainer中添加一个名为ApplicationContext的方法,如下所示:

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

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

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

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

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

相关问题