Reconfigure dependencies when Integration testing ASP.NET Core Web API and EF Core

GreenyMcDuff picture GreenyMcDuff · Apr 21, 2017 · Viewed 10.3k times · Source

I'm following this tutorial
Integration Testing with Entity Framework Core and SQL Server

My code looks like this

Integration Test Class

public class ControllerRequestsShould : IDisposable
{
    private readonly TestServer _server;
    private readonly HttpClient _client;
    private readonly YourContext _context;

    public ControllerRequestsShould()
    {
        // Arrange
        var serviceProvider = new ServiceCollection()
            .AddEntityFrameworkSqlServer()
            .BuildServiceProvider();

        var builder = new DbContextOptionsBuilder<YourContext>();

        builder.UseSqlServer($"Server=(localdb)\\mssqllocaldb;Database=your_db_{Guid.NewGuid()};Trusted_Connection=True;MultipleActiveResultSets=true")
            .UseInternalServiceProvider(serviceProvider);

        _context = new YourContext(builder.Options);
        _context.Database.Migrate();

        _server = new TestServer(new WebHostBuilder()
            .UseStartup<Startup>()
            .UseEnvironment(Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")));
        _client = _server.CreateClient();
    }

    [Fact]
    public async Task ReturnListOfObjectDtos()
    {
        // Arrange database data
        _context.ObjectDbSet.Add(new ObjectEntity{ Id = 1, Code = "PTF0001", Name = "Portfolio One" });
        _context.ObjectDbSet.Add(new ObjectEntity{ Id = 2, Code = "PTF0002", Name = "Portfolio Two" });

        // Act
        var response = await _client.GetAsync("/api/route");
        response.EnsureSuccessStatusCode();


        // Assert
        var result = Assert.IsType<OkResult>(response);            
    }

    public void Dispose()
    {
        _context.Dispose();
    }

As I understand it, the .UseStartUp method ensures the TestServer uses my startup class

The issue I'm having is that when my Act statement is hit

var response = await _client.GetAsync("/api/route");

I get an error in my startup class that the connection string is null. I think My understanding of the problem is that when my controller is hit from the client it injects my data repository, which in turn injects the db context.

I think I need to configure the service as part of the new WebHostBuilder section so that it used the context created in the test. But I'm not sure how to do this.

ConfigureServices method in Startup.cs

        public void ConfigureServices(IServiceCollection services)
    {
        // Add framework services
        services.AddMvc(setupAction =>
        {
            setupAction.ReturnHttpNotAcceptable = true;
            setupAction.OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter());
            setupAction.InputFormatters.Add(new XmlDataContractSerializerInputFormatter());
        });

        // Db context configuration
        var connectionString = Configuration["ConnectionStrings:YourConnectionString"];
        services.AddDbContext<YourContext>(options => options.UseSqlServer(connectionString));

        // Register services for dependency injection
        services.AddScoped<IYourRepository, YourRepository>();
    }

Answer

lilo.jacob picture lilo.jacob · May 20, 2018

@ilya-chumakov's answer is awesome. I just would like to add one more option

3. Use ConfigureTestServices method from WebHostBuilderExtensions.

The method ConfigureTestServices is available in the Microsoft.AspNetCore.TestHost version 2.1(on 20.05.2018 it is RC1-final). And it lets us override existing registrations with mocks.

The code:

_server = new TestServer(new WebHostBuilder()
    .UseStartup<Startup>()
    .ConfigureTestServices(services =>
    {
        services.AddTransient<IFooService, MockService>();
    })
);