我想使用InMemory数据库通过WebApplicationFactory测试我的Web api。
在测试中,我想在dbcontext中设置数据,调用我的api,测试数据。 这与获取数据或创建新条目的效果很好,但是在更新时,上下文不会更新,它仍包含数据的旧副本。 按照建议的here,使用InMemoryDatabaseRoot初始化了我的DbContext。
这是我的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();
});
}
}
我的测试类将其用作IClassFixture,因此它在我的测试中共享。
这是我的考试不及格
[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);
}
}
我也尝试在相同的范围内获得一个新的DbContext,但是失败说DbContext已被处置。
您可以找到完整的测试解决方案here。
答案 0 :(得分:1)
避免手动处理DbContext
,因为它由依赖项容器(DI)管理。换句话说,不要使用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
测试也失败了,因为控制器使用的DbContext
和单元测试中使用的DbContext
在不同的范围内。这是解决此问题的两种方法:
方法1: Reload()
:
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); }
方法2:创建另一个较小的范围以获取最新记录:
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); } }