无法使用相同的InMemory Db跨多个DbContext获取更新的数据

时间:2019-05-15 16:38:10

标签: asp.net-core entity-framework-core asp.net-core-webapi in-memory-database

我想使用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

1 个答案:

答案 0 :(得分:1)

  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
    
  2. 测试也失败了,因为控制器使用的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);
        }
    }