I want to use an InMemory database to test my web api using a WebApplicationFactory.
In my test I want to set up data in my dbcontext, call my api, test the data. This works fine with getting the data or creating new entries, but on update, the context is not updated, it still contains the old copy of the data. My DbContext is initialized with an InMemoryDatabaseRoot as suggested here.
Here is my WebApplicationFactory
public class InMemDbWebApplicationFactory : WebApplicationFactory<Startup>
{
public static readonly InMemoryDatabaseRoot InMemoryDatabaseRoot = new InMemoryDatabaseRoot();
public ServiceProvider ServiceProvider { get; private set; }
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
var serviceProvider = new ServiceCollection()
.AddEntityFrameworkInMemoryDatabase()
.BuildServiceProvider();
services.AddDbContext<TestDbContext>(options =>
{
options.UseInMemoryDatabase("InMemoryDb", InMemoryDatabaseRoot);
options.UseInternalServiceProvider(serviceProvider);
});
ServiceProvider = services.BuildServiceProvider();
});
}
}
My test class use it as a IClassFixture so it is shared across my tests.
Here is my failing test
[Fact]
public async void TestPutBook()
{
using (var scope = _serviceProvider.CreateScope())
using (var context = scope.ServiceProvider.GetRequiredService<TestDbContext>())
{
var book1 = new Book { Name = "Book1" };
var book2 = new Book { Name = "Book2" };
context.Books.AddRange(book1, book2);
context.SaveChanges();
var updateResp = await _httpClient.PutAsJsonAsync($"/api/books/{book1.Id}", "Book1Update");
updateResp.EnsureSuccessStatusCode();
var getResp = await _httpClient.GetAsync($"/api/books/{book1.Id}");
getResp.EnsureSuccessStatusCode();
var stringResponse = await getResp.Content.ReadAsStringAsync();
book1 = JsonConvert.DeserializeObject<Book>(stringResponse);
Assert.NotNull(book1);
//this works
Assert.Equal("Book1Update", book1.Name);
book1 = context.Books.Find(book1.Id);
Assert.NotNull(book1);
//this fails book1.Name is still equal to "Book1"
Assert.Equal("Book1Update", book1.Name);
}
}
I tried also to get a new DbContext within the same scope, but then it fails saying the DbContext is already disposed.
You can find a complete test solution here.
Avoid disposing DbContext
manually because it is managed by Dependency container (DI). In other words, don't use using
:
using (var context = scope.ServiceProvider.GetRequiredService()) { ... } // the context will be DISPOSED now! // now if you try to resolve the TestDbContext service in future, it throws // var context = scope.ServiceProvider.GetRequiredService(); // Wrong
The test fails also because the DbContext
used by controller & the DbContext
used in your unit test are in different scopes. Here're two approaches to solve this question:
Apprach 1 :Reload()
manually:
using (var scope = _serviceProvider.CreateScope()) { var context = scope.ServiceProvider.GetRequiredService<TestDbContext>(); var book1 = new Book { Name = "Book1" }; var book2 = new Book { Name = "Book2" }; context.Books.AddRange(book1, book2); context.SaveChanges(); ... ... book1 = context.Books.Find(book1.Id); context.Entry(book1).Reload(); // refresh to make sure we can get the newest book1 Assert.NotNull(book1); Assert.Equal("Book1Update", book1.Name); }
Approach 2: Create another smaller scope to get the newest record:
using (var scope = _serviceProvider.CreateScope()) { Book book1; var context = scope.ServiceProvider.GetRequiredService<TestDbContext>(); book1 = new Book { Name = "Book1" }; context.Books.AddRange(book1); context.SaveChanges(); var updateResp = await _httpClient.PutAsJsonAsync($"/api/books/{book1.Id}", "Book1Update"); updateResp.EnsureSuccessStatusCode(); // create a new smaller scope using(var scope2= scope.ServiceProvider.CreateScope()){ var context2 = scope2.ServiceProvider.GetRequiredService<TestDbContext>(); book1 = context2.Books.Find(book1.Id); Assert.NotNull(book1); Assert.Equal("Book1Update", book1.Name); } }