Introduction
You’re writing a Web API with ASP.NET Core 2.1, and use EF Core as your ORM.
If you follow the official guidance on doing integration tests in ASP.NET Core 2.1, then you can use either an in-memory database provider, or SQLite in-memory. We are using an in-memory database provider, which is setup in our CustomWebApplicationFactory as per the current Microsoft guidelines which are these:
public class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<RazorPagesProject.Startup> { protected override void ConfigureWebHost(IWebHostBuilder builder) { builder.ConfigureServices(services => { // Create a new service provider. var serviceProvider = new ServiceCollection() .AddEntityFrameworkInMemoryDatabase() .BuildServiceProvider(); // Add a database context (ApplicationDbContext) using an in-memory // database for testing. services.AddDbContext<ApplicationDbContext>(options => { options.UseInMemoryDatabase("InMemoryDbForTesting"); options.UseInternalServiceProvider(serviceProvider); });
These work well when running integration tests, as it allows you to quickly setup a database for each test, independent of every other test.
The problem
I couldn’t find any guidance on how to verify the data gets written correctly to the database in an assertion. e.g. here is one of our tests. We are using the XBehave library:
[Scenario] public void CreateApplication_ShouldReturn201() { "Given a valid create application request" .x(() => _fixture.GivenAValidCreateApplicationRequest()); "When an application is created" .x(() => _fixture.WhenAnApplicationIsCreated()); "Then the response http status code is a 201" .x(() => _fixture.ThenTheResponseStatusCodeIs(HttpStatusCode.Created)); "And the response should contain an id" .x(() => _fixture.ThenTheResponseShouldContainAnApplicationId()); "And the database should contain the application" .x(() => _fixture.ThenTheDatabaseShouldContainTheApplication()); }
How to verify the final step – “ThenTheDatabaseShouldContainTheApplication”?
The solution
Firstly, you need to change the call to AddDbContext so that your DbContext is a Singleton (the default is Scoped).
services.AddDbContext<ApplicationDbContext>(options => { options.UseInMemoryDatabase("InMemoryDbForTesting"); options.UseInternalServiceProvider(serviceProvider); }, ServiceLifetime.Singleton);
Then, you can get a DbContext from the system under test by asking its service collection for one, with a helper property like so:
protected ApplicationDbContext DbContext { get => TestServer.Host.Services.GetService(typeof(ApplicationDbContext)) as ApplicationDbContext; }
Since the DbContext is a singleton it’ll be the same one the system under test used, so we can query the DbContext for it directly.
internal void ThenTheDatabaseShouldContainTheApplication() { var application = DbContext.Applications.Find(ApplicationId); Assert.NotNull(application); }
This is a nice idea however it wouldn’t work when the DbContext instance is reused somewhere where child and parent service are using different scopes. https://stackoverflow.com/questions/51806077/cannot-consume-scoped-service-mydbcontext-from-singleton-invalidoperationexcep
Hi Anton. It’s been a few months since I blogged this, but I think you can leave you application code using a Scoped DbContext (the default). The code above where I tell it to use a Singleton is in my test code – the application code is unchanged – i.e. here’s the line from my Startup.cs:
services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(connectionString));
H Matt, thx. I managed to made it without singleton. There seems to be bug or undocumented behavior. You need to provide IDatabaseRoot (!?) when you create in memory database, otherwise you will get new instance of database each time you call GetService. See my comment here: https://github.com/aspnet/EntityFrameworkCore/issues/9613
For my tests that behaviour is desired, since it means each test has a clean database without needing to worry about database teardown. But I have noticed some alarming memory usage when running around 300 tests, so maybe I need to revisit that design.
Do you have a repository with the full code? My context is null, although I can access it to seed the database during ConfigureWebHost.
Sorry, I don’t have a full code sample as this was something I did at work.
Having trouble with this since 3.0. Now you have to access the services through a different means. Can you take a look at this?
https://github.com/bluebaroncanada/DependencyInjectionCore3Issue
“`
TestProject2.UnitTest1.Test1
System.InvalidOperationException : Cannot resolve scoped service ‘WebApplication.Model.ApplicationDbContext’ from root provider.
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteValidator.ValidateResolution(Type serviceType, IServiceScope scope, IServiceScope rootScope)
at Microsoft.Extensions.DependencyInjection.ServiceProvider.Microsoft.Extensions.DependencyInjection.ServiceLookup.IServiceProviderEngineCallback.OnResolve(Type serviceType, IServiceScope scope)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
at TestProject2.UnitTest1..ctor(CustomWebApplicationFactory`1 factory) in C:\Users\xxxxxxxxx\RiderProjects\WebApplication\TestProject2\UnitTest1.cs:line 19
“`
You now have to get the services like so:
“`
_db = factory.Services.GetService(typeof(ApplicationDbContext)) as
ApplicationDbContext;
“`
So I found out that if I take out the registering of the db in the actual Service class, then it works.
If I comment out Line 31 on Startup.cs, it works.