r3plica
r3plica

Reputation: 13367

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:

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?

Upvotes: 5

Views: 1174

Answers (2)

Yogi
Yogi

Reputation: 46

According to the official statement it is better to register the dependency again in Test

unit-testing

testing-with-per-request-dependencies

[TestClass]
public class UserTest
{
    private IContainer _container;
    public UserTest()
    {
        //Autofac
        var builder = new ContainerBuilder();
        builder.RegisterType<ADMSysDBContext>().SingleInstance();
        builder.RegisterType<UserServices>().As<IUserServices>();
        builder.RegisterType<AccountServices>().As<IAccountServices>();
        builder.RegisterType<AccountController>();
        _container = builder.Build();
    }
    [TestMethod]
    public void UserLoginTest()
    {
        try
        {
            AccountController acctController = _container.Resolve<AccountController>();
            var user = new User()
            {
                UserName = "sa",
                Password = "1234",
            };
            var userLogin_result = acctController.Login(user).Result;
            Assert.IsTrue(userLogin_result.success);
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

Upvotes: 1

r3plica
r3plica

Reputation: 13367

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.

Upvotes: 3

Related Questions