如何根據XUnit測試隔離EF InMemory數據庫

.net .net-core c# entity-framework-core in-memory-database

我正在嘗試使用InMemory EF7數據庫進行我的xunit存儲庫測試。

但我的問題是,當我嘗試Dispose創建的上下文時,內存db仍然存在。這意味著一個測試涉及其他。

我已經閱讀了本文單元測試實體框架7與內存數據存儲 ,我試圖在我的TestClass的構造函數中設置上下文。但這種方法不起作用。當我單獨運行測試時一切正常,但我的第一個測試方法是在DB中添加一些東西,第二個測試方法從之前的測試方法開始使用臟DB。我嘗試將IDispose添加到測試類中,但方法DatabaseContext和DB在內存中保留。我錯了什麼我錯過了什麼?

我的代碼看起來像:

using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Xunit;

namespace Fabric.Tests.Repositories
{
    /// <summary>
    /// Test for TaskRepository 
    /// </summary>
    public class TaskRepositoryTests:IDisposable
    {

        private readonly DatabaseContext contextMemory;

        /// <summary>
        /// Constructor
        /// </summary>
        public TaskRepositoryTests()
        {
            var optionsBuilder = new DbContextOptionsBuilder<DatabaseContext>();
            optionsBuilder.UseInMemoryDatabase();
            contextMemory = new DatabaseContext(optionsBuilder.Options);

        }

        /// <summary>
        /// Dispose DB 
        /// </summary>
        public void Dispose()
        {
            //this has no effect 
            if (contextMemory != null)
            {                
                contextMemory.Dispose();
            }
        }


        /// <summary>
        /// Positive Test for ListByAssigneeId method  
        /// </summary>
        /// <returns></returns>       
        [Fact]
        public async Task TasksRepositoryListByAssigneeId()
        {
            // Arrange
            var assigneeId = Guid.NewGuid();
            var taskList = new List<TaskItem>();


            //AssigneeId != assigneeId 
            taskList.Add(new TaskItem()
            {
                AssigneeId = Guid.NewGuid(),
                CreatorId = Guid.NewGuid(),
                Description = "Descr 2",
                Done = false,
                Id = Guid.NewGuid(),
                Location = "Some location 2",
                Title = "Some title 2"
            });

            taskList.Add(new TaskItem()
            {
                AssigneeId = assigneeId,
                CreatorId = Guid.NewGuid(),
                Description = "Descr",
                Done = false,
                Id = Guid.NewGuid(),
                Location = "Some location",
                Title = "Some title"
            });

            taskList.Add(new TaskItem()
            {
                AssigneeId = assigneeId,
                CreatorId = Guid.NewGuid(),
                Description = "Descr 2",
                Done = false,
                Id = Guid.NewGuid(),
                Location = "Some location 2",
                Title = "Some title 2"
            });

            //AssigneeId != assigneeId 
            taskList.Add(new TaskItem()
            {
                AssigneeId = Guid.NewGuid(),
                CreatorId = Guid.NewGuid(),
                Description = "Descr 2",
                Done = false,
                Id = Guid.NewGuid(),
                Location = "Some location 2",
                Title = "Some title 2"
            });


            //set up inmemory DB            
            contextMemory.TaskItems.AddRange(taskList);

            //save context
            contextMemory.SaveChanges();

            // Act
            var repository = new TaskRepository(contextMemory);
            var result = await repository.ListByAssigneeIdAsync(assigneeId);

            // Assert
            Assert.NotNull(result.Count());

            foreach (var td in result)
            {
                Assert.Equal(assigneeId, td.AssigneeId);
            }

        }

        /// <summary>
        /// test for Add method  
        /// (Skip = "not able to clear DB context yet")
        /// </summary>
        /// <returns></returns>
        [Fact]
        public async Task TasksRepositoryAdd()
        {
            var item = new TaskData()
            {
                AssigneeId = Guid.NewGuid(),
                CreatorId = Guid.NewGuid(),
                Description = "Descr",
                Done = false,
                Location = "Location",
                Title = "Title"
            };


            // Act
            var repository = new TaskRepository(contextMemory);
            var result = await repository.Add(item);

            // Assert
            Assert.Equal(1, contextMemory.TaskItems.Count());
            Assert.NotNull(result.Id);

            var dbRes = contextMemory.TaskItems.Where(s => s.Id == result.Id).SingleOrDefault();
            Assert.NotNull(dbRes);
            Assert.Equal(result.Id, dbRes.Id);
        }


    }
}

我在用:

"Microsoft.EntityFrameworkCore.InMemory": "1.0.0"

"Microsoft.EntityFrameworkCore": "1.0.0"

"xunit": "2.2.0-beta2-build3300"

一般承認的答案

文檔中

通常,EF為AppDomain中給定類型的所有上下文創建單個IServiceProvider - 這意味著所有上下文實例共享相同的InMemory數據庫實例。通過允許傳入一個,您可以控制InMemory數據庫的範圍。

而不是使測試類是一次性的並嘗試以這種方式處理數據上下文,為每個測試創建一個新的:

private static DbContextOptions<BloggingContext> CreateNewContextOptions()
{
    // Create a fresh service provider, and therefore a fresh 
    // InMemory database instance.
    var serviceProvider = new ServiceCollection()
        .AddEntityFrameworkInMemoryDatabase()
        .BuildServiceProvider();

    // Create a new options instance telling the context to use an
    // InMemory database and the new service provider.
    var builder = new DbContextOptionsBuilder<DatabaseContext>();
    builder.UseInMemoryDatabase()
           .UseInternalServiceProvider(serviceProvider);

    return builder.Options;
}

然後,在每個測試中,使用此方法新建數據上下文:

using (var context = new DatabaseContext(CreateNewContextOptions()))
{
    // Do all of your data access and assertions in here
}

這種方法應該為每個測試提供一個乾淨的內存數據庫。


熱門答案

我認為Nate給出的答案現在可能已經過時,或者我做錯了。 UseInMemoryDatabase()現在需要db名稱。

以下是我最終的結果。我添加了一行來創建一個唯一的數據庫名稱。我刪除了using語句,轉而使用為每個測試用例調用一次的構造函數和dispose。

我的測試中有一些調試行。

public class DeviceRepositoryTests : IClassFixture<DatabaseFixture>, IDisposable
{

    private readonly DeviceDbContext _dbContext;
    private readonly DeviceRepository _repository;

    private readonly ITestOutputHelper _output;
    DatabaseFixture _dbFixture;

    public DeviceRepositoryTests(DatabaseFixture dbFixture, ITestOutputHelper output)
    {
        this._dbFixture = dbFixture;
        this._output = output;

        var dbOptBuilder = GetDbOptionsBuilder();
        this._dbContext = new DeviceDbContext(dbOptBuilder.Options);
        this._repository = new DeviceRepository(_dbContext);

        DeviceDbContextSeed.EnsureSeedDataForContext(_dbContext);
        //_output.WriteLine($"Database: {_dbContext.Database.GetDbConnection().Database}\n" +
        _output.WriteLine($"" +
              $"Locations: {_dbContext.Locations.Count()} \n" +
              $"Devices: {_dbContext.Devices.Count()} \n" +
              $"Device Types: {_dbContext.DeviceTypes.Count()} \n\n");

        //_output.WriteLine(deviceDbContextToString(_dbContext));
    }

    public void Dispose()
    {
        _output.WriteLine($"" +
                          $"Locations: {_dbContext.Locations.Count()} \n" +
                          $"Devices: {_dbContext.Devices.Count()} \n" +
                          $"Device Types: {_dbContext.DeviceTypes.Count()} \n\n");
        _dbContext.Dispose();
    }

    private static DbContextOptionsBuilder<DeviceDbContext> GetDbOptionsBuilder()
    {

        // The key to keeping the databases unique and not shared is 
        // generating a unique db name for each.
        string dbName = Guid.NewGuid().ToString();

        // Create a fresh service provider, and therefore a fresh 
        // InMemory database instance.
        var serviceProvider = new ServiceCollection()
            .AddEntityFrameworkInMemoryDatabase()
            .BuildServiceProvider();

        // Create a new options instance telling the context to use an
        // InMemory database and the new service provider.
        var builder = new DbContextOptionsBuilder<DeviceDbContext>();
        builder.UseInMemoryDatabase(dbName)
               .UseInternalServiceProvider(serviceProvider);

        return builder;
    }

這是一個非常基本的測試用例。

[Fact]
public void LocationExists_True()
{
    Assert.True(_repository.LocationExists(_dbFixture.GoodLocationId));
}

我還做了8個測試用例試圖刪除具有相同id的相同設備並且每個都通過了。



Related

許可下: CC-BY-SA with attribution
不隸屬於 Stack Overflow
許可下: CC-BY-SA with attribution
不隸屬於 Stack Overflow