使用具有预加载功能的内存数据库对EF Core进行单元测试

时间:2019-08-12 22:56:13

标签: c# unit-testing asp.net-core entity-framework-core xunit

我正在为我的Web API编写单元测试,除非删除包含(从方法中重新加载),否则无法通过测试。我正在使用内存数据库来提供dbcontext,无法弄清为什么它不返回任何数据。在此先感谢您的帮助或建设性的批评

这是我要测试的方法。
注意:如果我注释掉.include语句,它将通过测试。

    public async Task<LibraryAsset> GetAsset(int assetId)
    {
        var asset = await _context.LibraryAssets
            .Include(p => p.Photo)
            .Include(p => p.Category)
            .Include(a => a.AssetType)
            .Include(s => s.Status)
            .Include(s => s.Author)
            .FirstOrDefaultAsync(x => x.Id == assetId);

        return asset;
    }

这是使用inMemory数据库的基础DbContext

    public DataContext GetDbContext()
    {
        var builder = new DbContextOptionsBuilder<DataContext>();

        if (useSqlite)
        {
            // Use Sqlite DB.
            builder.UseSqlite("DataSource=:memory:", x => { });
        }
        else
        {
            // Use In-Memory DB.
            builder.UseInMemoryDatabase(Guid.NewGuid().ToString());
        }

        var DataContext = new DataContext(builder.Options);

        if (useSqlite)
        {
            // SQLite needs to open connection to the DB.
            // Not required for in-memory-database and MS SQL.
            DataContext.Database.OpenConnection();
        }

        DataContext.Database.EnsureCreated();

        return DataContext;
    }

这是测试:

    [Fact]
    public async void GetAssetById_ExistingAsset_ReturnAsset()
    {
        using (var context = GetDbContext())
        {
            ILogger<LibraryAssetService> logger = new 
            NullLogger<LibraryAssetService>();

            var service = new LibraryAssetService(context, _logger);

            var asset = new LibraryAsset
            {
                Id = 40,
                NumberOfCopies = 20,
                Title = "",
                Year = 1992,
                Status = new Status { Id = 1 },
                AssetType = new AssetType { Id = 1 },
                Author = new Author { Id = 1 },
                Category = new Category { Id = 2 },
                Photo = new AssetPhoto { Id = 1 }
            };

            context.LibraryAssets.Attach(asset);

            context.Add(asset);
            context.SaveChanges();

            var actual = await service.GetAsset(40);
            Assert.Equal(40, actual.Id);
        }
    }

这是我第一次编写单元测试,基本上我正在学习。请随时指出您可能已经注意到的任何其他错误。

1 个答案:

答案 0 :(得分:1)

您的代码存在一些问题:

  1. 如果您的实际数据库是关系数据库,请避免使用UseInMemoryDatabase数据库进行测试,因为它不支持关系数据库的行为。
  2. 将“安排”上下文与《法案》上下文分开。这意味着,创建一个新的DataContext来准备测试,添加测试数据等,并为SUT创建另一个(在这种情况下为LibraryAssetService)。 DbContext将本地数据(存储在内存中)存储在数据库中可能不存在,并且在某些情况下可能显示伪造的绿色测试!
  3. 添加资产时不需要Attach。这可能会导致sqlite产生Foreign key constraint错误。

为简单起见,我删除了一些导航和参数。因此,假设LibraryAssetService是这样的:

public class LibraryAssetService
{
  public LibraryAssetService(DataContext context)
  {
     _context = context;
  }

  private readonly DataContext _context;

  public async Task<LibraryAsset> GetAsset(int assetId)
  {
     var asset = await _context.LibraryAssets
        .Include(p => p.Photo)
        .Include(s => s.Author)
        .FirstOrDefaultAsync(x => x.Id == assetId);

     return asset;
  }
}

测试类:

public class LibraryAssetServiceTests
{
  public LibraryAssetServiceTests()
  {
     _factory = new TestDataContextFactory();
  }

  private TestDataContextFactory _factory;

  [Fact]
  public async void GetAssetById_ExistingAsset_ReturnAsset()
  {
     // Arrange
     using (var context = _factory.Create())
     {
        var asset = new LibraryAsset
        {
           Id = 40,
           Author = new Author { Id = 1 },
           Photo = new Photo { Id = 1 }
        };

        context.Add(asset);
        context.SaveChanges();
     }

     // Act
     using (var context = _factory.Create())
     {
        var service = new LibraryAssetService(context);
        var actual = await service.GetAsset(40);

        // Assert
        Assert.Equal(40, actual.Id);
        Assert.Equal(1, actual.Author.Id);
        Assert.Equal(1, actual.Photo.Id);
     }

  }
}

最后,提供一些帮助程序类来为测试准备DataContext。最好在测试类之外提取此类内容。在使用sqlite内存数据库进行测试时,要记住的重要要点是,在测试期间应保持连接打开。无论您创建多少个DbContext实例。 xUnit为每个测试方法创建测试类的实例。因此,将为每个测试创建一个TestDataContextFactory的实例,一切顺利。

public class TestDataContextFactory
{
  public TestDataContextFactory()
  {
     var builder = new DbContextOptionsBuilder<DataContext>();
     var connection = new SqliteConnection("DataSource=:memory:");
     connection.Open();
     builder.UseSqlite(connection);

     using (var ctx = new DataContext(builder.Options))
     {
        ctx.Database.EnsureCreated();
     }

     _options = builder.Options;
  }

  private readonly DbContextOptions _options;

  public DataContext Create() => new DataContext(_options);
}
相关问题