Search code examples
c#testingasp.net-web-apiintegration-testingautofac

Using Autofac in integration tests with web api 2


I have been manually instantiating my services in my integration tests, but when I got to a serve that had Lazy dependencies, I did some research and found that you can actually use Autofac to resolve your services when doing your tests.

So, I wrote this class:

public class Container<TModule> where TModule: IModule, new()
{
    private readonly IContainer _container;

    protected Container()
    {
        var builder = new ContainerBuilder();
        builder.RegisterModule(new TModule());
        _container = builder.Build();
    }

    protected TEntity Resolve<TEntity>() => _container.Resolve<TEntity>();
    protected void Dispose() => _container.Dispose();
}

And then in my context, I changed to this:

public class ProductContext : Container<AutofacModule>
{
    public IProductProvider ProductProvider { get; }
    public static ProductContext GiventServices() => new ProductContext();

    protected ProductContext()
    {
        ProductProvider = Resolve<IProductProvider>();
    }

    public List<JObject> WhenListProducts(int categoryId) => ProductProvider.List(categoryId);
}

I have another context that seems to work (the tests pass) and that is using a MatchProvider. If I compare both in my Autofac module, they look like this:

builder.RegisterType<ProductProvider>().As<IProductProvider>().InstancePerRequest();

and

builder.RegisterType<MatchProvider>().As<IMatchProvider>().SingleInstance();

Because the MatchProvider is a singelton, it seems to have no issues being resolved, but the ProductProvider is an instance per request, this is where the issue seems to lie.

I get this error when running any tests that require that service:

No scope with a tag matching 'AutofacWebRequest' is visible from the scope in which the instance was requested.

I figured it was because I didn't have the right nuget packages installed. So I installed:

  • Autofac
  • Autofac.Integration.Owin
  • Autofac.Integration.WebApi
  • Autofac.Integration.WebApi.Owin

These are the same references that are used where my module is defined, but this did not help. Does anyone know what I need to do to get this to work?


Solution

  • I couldn't find a suitable (easy) solution to this. I saw some people creating lifetime scopes themselves, which to me seemed like overkill and it wasn't "nice" code. So, taking one of Autofac's principles: Any service that is registered multiple times; the last instance is the instance that is resolved.

    So in my Container class, I just re-registered my InstancePerRequest services as InstancePerDependency instead. This solved my issue. Here is my full code:

    public class ContainerContext<TModule> where TModule: IModule, new()
    {
        private IContainer _container;
    
        protected ContainerContext()
        {
            var builder = new ContainerBuilder();
            builder.RegisterModule(new TModule());
    
            // Because Autofac will resolve services using the last registration, we can change all our web api 
            // services to by InstancePerDependency instead of InstancePerRequest which is obviously better
            // when testing.
            builder.RegisterType<AnswerProvider>().As<IAnswerProvider>().InstancePerDependency();
            builder.RegisterType<AttributeProvider>().As<IAttributeProvider>().InstancePerDependency();
            builder.RegisterType<CategoryProvider>().As<ICategoryProvider>().InstancePerDependency();
            builder.RegisterType<ClaimProvider>().As<IClaimProvider>().InstancePerDependency();
            builder.RegisterType<ClientProvider>().As<IClientProvider>().InstancePerDependency();
            builder.RegisterType<CriteriaProvider>().As<ICriteriaProvider>().InstancePerDependency();
            builder.RegisterType<FeedProvider>().As<IFeedProvider>().InstancePerDependency();
            builder.RegisterType<FormulaProvider>().As<IFormulaProvider>().InstancePerDependency();
            builder.RegisterType<ImageProvider>().As<IImageProvider>().InstancePerDependency();
            builder.RegisterType<GroupProvider>().As<IGroupProvider>().InstancePerDependency();
            builder.RegisterType<QuestionProvider>().As<IQuestionProvider>().InstancePerDependency();
            builder.RegisterType<StripeProvider>().As<IStripeProvider>().InstancePerDependency();
    
            builder.RegisterType<ApiAiProvider>().As<IApiAiProvider>().InstancePerDependency();
            builder.RegisterType<PiiikProvider>().As<IPiiikProvider>().InstancePerDependency();
            builder.RegisterType<ProductProvider>().As<IProductProvider>().InstancePerDependency();
            builder.RegisterType<SettingProvider>().As<ISettingProvider>().InstancePerDependency();
            builder.RegisterType<TrackingProvider>().As<ITrackingProvider>().InstancePerDependency();
    
            _container = builder.Build();
        }
    
        protected TEntity Resolve<TEntity>() => _container.Resolve<TEntity>();
        protected void Dispose() => _container.Dispose();
    }
    

    And then, any context I use inherits this class:

    public class ProductContext : ContainerContext<AutofacModule>
    {
        public IProductProvider ProductProvider { get; }
        public static ProductContext GiventServices() => new ProductContext();
    
        protected ProductContext()
        {
            ProductProvider = Resolve<IProductProvider>();
        }
    
        public List<JObject> WhenListProducts(int categoryId) => ProductProvider.List(categoryId);
    }
    

    Which means, when testing, I can just do this:

    [TestFixture]
    public class ProductProviderTests
    {
        [Test]
        public void ShouldHaveProducts()
        {
            var services = ProductContext.GiventServices();
            var products = services.WhenListProducts(1);
            products.Count.Should().NotBe(0);
        }
    
        [Test]
        public void ShouldHaveDuplicateVariants()
        {
            var services = ProductContext.GiventServices();
            var products = services.WhenListProducts(1);
            var anyDuplicate = products.GroupBy(x => x.SelectToken("variant").ToString()).Any(g => g.Count() > 1);
            anyDuplicate.Should().Be(true);
        }
    
        [Test]
        public void ShouldNotHaveDuplicateGtins()
        {
            var services = ProductContext.GiventServices();
            var products = services.WhenListProducts(1);
            var anyDuplicate = products.GroupBy(x => x.SelectToken("gtin").ToString()).Any(g => g.Count() > 1);
            anyDuplicate.Should().Be(false);
        }
    }
    

    This should help anyone else having the same issue.