Search code examples
asp.net-coredependency-injectionentity-framework-corexunitsimple-injector

How do I build tests using xUnit for an Asp.NetCore WebAPI built with Entity Framework Core and Simple Injector?


I have created an ASP.NET Core Web API using Entity Framework Core and Simple Injector.

I would like unit tests using xUnit to test my controllers.

I'm not sure where to begin. I believe that I have to mock a container object in my unit tests.

Here is the start up code where the container gets initialized:

public class Startup
{
    private Container container;
    public IConfiguration Configuration { get; }
    private IConfigurationRoot configurationRoot;    

    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;

        // Build configuration info
        configurationRoot = new ConfigurationBuilder()
            .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
            .Build();
    }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers();
        InitializeContainer(); 

        services.AddSimpleInjector(container, options =>
        {
            options.AddAspNetCore()
            .AddControllerActivation();
            options.AddLogging();
        });
    }

    private void InitializeContainer()
    {
        container = new SimpleInjector.Container();
        container.Options.ResolveUnregisteredConcreteTypes = false;
        container.ConfigureServices();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseHttpsRedirection();
        app.UseRouting();
        app.UseAuthorization();
        app.UseSimpleInjector(container);
        AppSettingsHelper.AppConfig = configurationRoot;
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }
}

Here is the code for my services installer:

public static class ServicesInstaller
{
    public static void ConfigureServices(this Container container)
    {
        container.Options.DefaultScopedLifestyle = new AsyncScopedLifestyle();

        //Assembly.Load will not re-load already loaded Assemblies
        container.Register<IFooContext, FooContext>(Lifestyle.Scoped);
        container.Register<FooContext>(Lifestyle.Scoped);
    }
}

Here is a sample controller:

[Route("[controller]")]
[ApiController]
public class SomeController : ControllerBase
{
    private readonly ILogger<SomeController> _logger;
    private readonly Container _container;

    public SomeController(ILogger<SomeController> p_Logger, Container p_Container)
    {
        _logger = p_Logger;
        _container = p_Container;
    }
    
    [HttpGet]
    [Route("{p_SomeId}")]
    public Some GetOwnerByOwnerId(Guid p_SomeId)
    {
        Some some;
        using (Scope scope = AsyncScopedLifestyle.BeginScope(_container))
        {
            var dbContext = _container.GetInstance<FooContext>();
            some  = dbContext.Somes.Where(x => x.SomeId == p_SomeId).FirstOrDefault();
        }
        return some;
    }
}

I'm relatively new to using SimpleInjector.

How would I mock up a container to use for testing?


Solution

  • The controller in the provided example should not be coupled to anything container specific.

    Explicitly inject the necessary dependencies into the controller.

    [Route("[controller]")]
    [ApiController]
    public class SomeController : ControllerBase {
        private readonly ILogger<SomeController> _logger;
        private readonly IFooContext dbContext;
    
        public SomeController(ILogger<SomeController> p_Logger, IFooContext dbContext) {
            _logger = p_Logger;
            this.dbContext = dbContext;
        }
        
        [HttpGet]
        [Route("{p_SomeId}")]
        public Some GetOwnerByOwnerId(Guid p_SomeId) {
            Some some = dbContext.Somes.Where(x => x.SomeId == p_SomeId).FirstOrDefault();
            return some;
        }
    }
    

    Now there is no need to mock the container, which would be seen as an implementation detail code smell.

    Mock the dependency abstractions and verify the expected behavior when exercising your unit test(s).

    Controllers should also be kept as lean as possible since most other cross-cutting concerns, like making sure the injected context is scoped, are handled by the framework via the configured container at startup.