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

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

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

这是我试图测试的方法。
备注 : 如果我注释掉 .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;
    }

这是底座DbContext使用 inMemory 数据库:
    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);
        }
    }

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

最佳答案

您的代码存在一些问题:

  • 如果您的真实数据库是关系数据库,请避免使用 UseInMemoryDatabase用于测试的数据库,因为它不支持关系行为。
  • 将Arrange 上下文与Act 上下文分开。这意味着,为准备测试、添加测试数据等创建一个新的 DataContext,并为 SUT 创建另一个 DataContext(在本例中为 LibraryAssetService)。 DbContext存储数据库中可能不存在的本地数据(在内存中),并且在某些情况下可能会显示虚假的绿色测试!
  • 你不需要Attach当您添加 Assets 时。那可能会产生 Foreign key constraint sqlite 错误。

  • 为简单起见,我删除了您的一些导航和参数。所以让我们假设 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);
    }
    

    关于c# - 使用具有预加载功能的内存数据库对 EF Core 进行单元测试,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57469212/

    相关文章:

    perl - 测试::简单检查文件是否存在

    python - 如何使用 PyDev 调试 Django 单元测试?

    c# - ASP.NET Core 6 中的非缓冲输出

    c# - System.Net.Http.HttpClient 添加 Request-Id header

    c# - 如何使用 LINQ 查询包含多个子表?

    c# - 如何在 C# 中更新 IEnumerable 列表的特定元素的属性

    c# - asp .net c# 中的 jquery 动画缩放功能

    c# - 将 int 转换为枚举的正确方法

    c# - 如何验证调用方法时是否引发了操作(使用 Moq 进行单元测试)

    azure - 需要在 Azure Service Fabric 上实现 ICommunicationListener 的 ZeroMQ