使用具有预加载功能的内存数据库对 EF Core 进行单元测试 [英] Unit testing EF Core using in-memory database with an eager-loaded function

查看:32
本文介绍了使用具有预加载功能的内存数据库对 EF Core 进行单元测试的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I am writing unit tests for my my Web API and cannot get the test to pass except by removing the include (eager-loading from the method). I am using the in-memory database to provide the dbcontext and can't figure out why it is returning no data. Thanks in advance for any help or constructive criticism

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

This is the method I am trying to test.
Note: it passes the test if I comment out the .include statements.

    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 DB 的基本 DbContext:

This is the base DbContext using the inMemory DB:

    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);
        }
    }

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

This is my first time writing unit tests and I am basically learning as I go. Please feel free to point out any other mistakes that you may have noticed as well.

推荐答案

您的代码存在一些问题:

There are some issues with your code:

  1. 如果您的真实数据库是关系数据库,请避免使用 UseInMemoryDatabase 数据库进行测试,因为它不支持关系行为.
  2. 将Arrange 上下文与Act 上下文分开.这意味着,为准备测试、添加测试数据等创建一个新的 DataContext,并为 SUT 创建另一个 DataContext(在本例中为 LibraryAssetService).DbContext 存储本地数据(在内存中),这些数据可能不存在于数据库中,并且在某些情况下可能会显示虚假的绿色测试!
  3. 添加资产时不需要Attach.这可能会使用 sqlite 创建 Foreign key constraint 错误.
  1. If your real databse is relational avoid using UseInMemoryDatabase database for testing because it doesn't support relational behaviours.
  2. Separate the Arrange contexts from the Act contexts. That means, create a new DataContext for preparing the test, adding test data, and etc, and create another one for SUT (LibraryAssetService in this case). DbContext stores local data (in memory) which may not exist in the database and that could show fake green tests in some scenarios!
  3. You don't need Attach when you're adding the assets. That could create Foreign key constraint error with sqlite.

为了简单起见,我删除了您的一些导航和参数.所以让我们假设 LibraryAssetService 是这样的:

I removed some of your navigations and parameters for the sake of simplicity. So lets suppose the LibraryAssetService is something like this:

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 实例,您就可以开始了.

And finally, a little helper class to prepare the DataContext for your tests. It's good practice to extract these kind of things outside your test classes. The important thing to remember when testing with sqlite memory databases is that you should keep the connection open during the test. No matter how many DbContext instances you create. The xUnit create an instance of the test class for each test method. So an instance of TestDataContextFactory will be created for each test, and you are good to go.

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);
}

这篇关于使用具有预加载功能的内存数据库对 EF Core 进行单元测试的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆