2

I have trouble with testing my application using Owin.TestServer. I couldn't find anything useful, and I hope this is an easy fix that community can help with :)

Recently I started to write integration tests for my WebApi application that is using OWIN and AutoFac for DI. I have 3 integration tests in total. When I run each test individually, they are all passing. However when I run all of the tests at once, only first one succeeds and the other two are failed because of the following AutoFac error:

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 indicates that the error comes from the AutoFac middleware for Owin.

I have following setup for the tests:

[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 test will execute properly, but the second one will fail with message stated above.

I have tried with different registration scopes of the dependecies, but it didn't help. Below is my AutoFac configuration, startup class and sample AutoFac module:

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

Sample AutoFac module:

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

EDIT - SOLUTION

What @Eris suggested made a lot of sense - my AutoFacConfig class was using static methods and members, which meant that the Container property was present on subsequent tests and it was not being created again and it was marked as disposed.

I decided to refactor the code so AutoFacConfig is not using static members any more, as I didn't want to play with disposal of the container on application shutdown.

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);
    }
}
jjczopek
  • 3,319
  • 2
  • 32
  • 72
  • If you invoke two methods in one, would you get error? – Anton Putau Sep 20 '15 at 15:27
  • is there a specific reason you're recreating `_server` and `_transactionscope` for each test, as opposed to one server per test fixture, assembly, etc? – Eris Sep 20 '15 at 16:25
  • @Eris - I thought thats the way to do it, especially with the transaction scope in order keep the databse consistent between tests as some of them manipulate database. – jjczopek Sep 20 '15 at 17:32

1 Answers1

3

I suspect it's the AutoFacConfig causing a problem:

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

In this case, the _container is not null, but in "Disposed" state. It should work if you unconditionally recreate it. (I'm not familiar with C#6 syntax yet, so this may not be entirely correct)

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

Alternate answer: 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
            });
        }
    }
}
Community
  • 1
  • 1
Eris
  • 7,378
  • 1
  • 30
  • 45
  • I think it might be the case, will check it out. However I would suspect that disposing the test sever would dispose whole app with the container, but it effictively is an singleton, that might be the case. I'll get back with the results after refactoring. – jjczopek Sep 20 '15 at 18:19
  • 1
    Yeah, disposing static items is tricky business, and the internals are outside my realm of current knowledge. – Eris Sep 20 '15 at 18:20
  • Worked like a charm - I'll update the question with solution – jjczopek Sep 20 '15 at 19:53
  • thanks, this pointed me in the right direction on a similar issue – Jota.Toledo Mar 31 '18 at 16:33