【问题标题】:WebApi2 with OWIN TestServer and AutoFac - LifetimeScope already disposed带有 OWIN TestServer 和 AutoFac 的 WebApi2 - LifetimeScope 已部署
【发布时间】:2015-09-20 10:21:56
【问题描述】:

我无法使用Owin.TestServer 测试我的应用程序。我找不到任何有用的东西,我希望这是一个简单的解决方法,社区可以提供帮助:)

最近我开始为我的 WebApi 应用程序编写集成测试,该应用程序使用 OWIN 和 AutoFac 进行 DI。我总共有 3 个集成测试。当我单独运行每个测试时,它们都通过了。但是,当我一次运行所有测试时,只有第一个成功,其他两个失败,因为以下 AutoFac 错误:

System.AggregateException: One or more errors occurred. ---> 
System.ObjectDisposedException: Instances cannot be resolved and nested lifetimes cannot be created from this LifetimeScope as it has already been disposed.

Stacktrace 表明错误来自 Owin 的 AutoFac 中间件。

我有以下测试设置:

[TestClass]
public class DinnerListControllerTests
{
    private TestServer _server;
    private TransactionScope _transactionScope;

    [TestInitialize]
    public void Init()
    {
        _transactionScope = new TransactionScope(TransactionScopeOption.RequiresNew);
        _server = TestServer.Create<Startup>();
    }

    [TestCleanup]
    public void Dispose()
    {
        _server?.Dispose();
        _transactionScope?.Dispose();
    }

    [TestMethod]
    public void GetAllLists()
    {
        var response = _server.HttpClient.GetAsync("/api/dinnerlists").Result;
        response.IsSuccessStatusCode.Should().BeTrue("there should be no error");
        var result = response.Content.ReadAsAsync<IEnumerable<DinnerListDTO>>().Result;
        result.Should().NotBeNull().And.HaveCount(5);
    }

    [TestMethod]
    public void GetActiveListsReturnsTwoLists()
    {
        var response = _server.HttpClient.GetAsync("/api/dinnerlists/active").Result;
        response.IsSuccessStatusCode.Should().BeTrue();

        var result = response.Content.ReadAsAsync<IEnumerable<DinnerListDTO>>().Result;
        result.Should()
            .NotBeNullOrEmpty()
            .And.HaveCount(2)
            .And.OnlyContain(dto => dto.OpenUntil.CompareTo(DateTime.Now) > 0);
    }
}

GetAllLists 测试将正确执行,但第二个测试将失败并显示上述消息。

我尝试过不同的依赖项注册范围,但没有帮助。下面是我的 AutoFac 配置、启动类和示例 AutoFac 模块:

AutoFacConfig.cs

public class AutoFacConfig
{
    private static IContainer _container;
    public static IContainer Container => _container ?? (_container = BuildContainer());

    public static void ConfigureAutoFac(HttpConfiguration config)
    {
        if (config == null)
            throw new ArgumentNullException(nameof(config));

        FluentValidationModelValidatorProvider.Configure(config,
            provider => provider.ValidatorFactory = new AutoFacValidatorFactory(Container));

        config.DependencyResolver = new AutofacWebApiDependencyResolver(Container);
    }

    private static IContainer BuildContainer()
    {
        var autoFacBuilder = new ContainerBuilder();
        var assembly = Assembly.GetExecutingAssembly();
        autoFacBuilder.RegisterApiControllers(assembly).InstancePerRequest();

        autoFacBuilder.RegisterType<DinnerDbContext>().InstancePerRequest();
        autoFacBuilder.RegisterModule<RepositoryModule>();
        autoFacBuilder.RegisterModule<ServicesModule>();
        autoFacBuilder.RegisterModule<ValidationModule>();
        autoFacBuilder.RegisterModule<AutoMapperModule>();
        autoFacBuilder.RegisterModule<AutofacWebTypesModule>();

        return autoFacBuilder.Build();
    }
}

Startup.cs:

public class Startup
{
    public void Configuration(IAppBuilder appBuilder)
    {
        var httpConfiguration = new HttpConfiguration();

        WebApiConfig.Register(httpConfiguration);
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);

        AutoFacConfig.ConfigureAutoFac(httpConfiguration);
        AutoMapperConfig.RegisterMappings();

        appBuilder.UseAutofacMiddleware(AutoFacConfig.Container);
        appBuilder.UseAutofacWebApi(httpConfiguration);
        appBuilder.UseWebApi(httpConfiguration);
    }
}

AutoFac 模块示例:

public class RepositoryModule : Module
{
    protected override void Load(ContainerBuilder builder)
    {
        var assembly = System.Reflection.Assembly.GetExecutingAssembly();
        builder.RegisterAssemblyTypes(assembly)
            .Where(type => type.Name.EndsWith("Repository"))
            .AsImplementedInterfaces()
            .InstancePerRequest();
    }
}

编辑 - 解决方案

@Eris 的建议很有意义 - 我的 AutoFacConfig 类使用静态方法和成员,这意味着 Container 属性出现在后续测试中,并且不会再次创建它,它被标记为处置。

我决定重构代码,因此 AutoFacConfig 不再使用静态成员,因为我不想在应用程序关闭时处理容器。

AutoFacConfig.cs:

public class AutoFacConfig
{
    private IContainer _container;
    public IContainer Container
    {
        get { return _container ?? (_container = BuildContainer()); }
    }

    public void ConfigureAutoFac(HttpConfiguration config)
    {
        //...
    }

    private IContainer BuildContainer()
    {
        var autoFacBuilder = new ContainerBuilder();
        //...
        return autoFacBuilder.Build();
    }
}

Startup.cs

public class Startup
{
    public void Configuration(IAppBuilder appBuilder)
    {
        var httpConfiguration = new HttpConfiguration();
        var autofacConfig = new AutoFacConfig();  // create instance of AutoFacConfig           
        autofacConfig.ConfigureAutoFac(httpConfiguration); // configure autofac

        appBuilder.UseAutofacMiddleware(autofacConfig.Container);
        appBuilder.UseAutofacWebApi(httpConfiguration);
        appBuilder.UseWebApi(httpConfiguration);
    }
}

【问题讨论】:

  • 如果同时调用两个方法会报错吗?
  • 您为每个测试重新创建 _server_transactionscope 是否有特定原因,而不是每个测试夹具、程序集等一个服务器?
  • @Eris - 我认为这就是这样做的方法,特别是在事务范围内,以便在其中一些操作数据库时保持测试之间的数据库一致。

标签: c# integration-testing asp.net-web-api2 owin autofac


【解决方案1】:

我怀疑是 AutoFacConfig 引起了问题:

    public static IContainer Container => _container ?? (_container = BuildContainer());

在这种情况下,_container 不为空,而是处于“已处理”状态。如果您无条件地重新创建它,它应该可以工作。 (我还不熟悉 C#6 语法,所以这可能不完全正确)

    public static IContainer Container => _container = BuildContainer();

替代答案: In self-hosted OWIN Web API, how to run code at shutdown?

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        var context = new OwinContext(app.Properties);
        var token = context.Get<CancellationToken>("host.OnAppDisposing");
        if (token != CancellationToken.None)
        {
            token.Register(() =>
            {
                // code to run 
                // null out disposable resources
            });
        }
    }
}

【讨论】:

  • 我认为可能是这种情况,会检查一下。但是,我怀疑处置测试服务器会将整个应用程序与容器一起处置,但它实际上是一个单例,可能就是这种情况。我会在重构后返回结果。
  • 是的,处理静态项目是一件棘手的事情,而且内部结构超出了我目前的知识范围。
  • 工作就像一个魅力 - 我会用解决方案更新问题
  • 谢谢,这为我在类似问题上指明了正确的方向
猜你喜欢
  • 2014-12-18
  • 1970-01-01
  • 1970-01-01
  • 2023-04-09
  • 1970-01-01
  • 2014-10-14
  • 1970-01-01
  • 1970-01-01
  • 2015-06-29
相关资源
最近更新 更多