C# 单元测试EF核心,使用内存中的数据库和一个急切加载的函数

C# 单元测试EF核心,使用内存中的数据库和一个急切加载的函数,c#,unit-testing,asp.net-core,entity-framework-core,xunit,C#,Unit Testing,Asp.net Core,Entity Framework Core,Xunit,我正在为我的Web API编写单元测试,除非删除include(从方法中急切加载),否则无法通过测试。我正在使用内存中的数据库来提供dbcontext,但不明白它为什么不返回任何数据。提前感谢您的帮助或建设性的批评 这是我正在尝试测试的方法。 注意:如果我注释掉。include语句,它将通过测试 public async Task<LibraryAsset> GetAsset(int assetId) { var asset = await _cont

我正在为我的Web API编写单元测试,除非删除include(从方法中急切加载),否则无法通过测试。我正在使用内存中的数据库来提供
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;
    }
这就是测试:

    [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);
        }
    }
[事实]
public async void GetAssetById\u ExistingAsset\u ReturnAsset()
{
使用(var context=GetDbContext())
{
ILogger记录器=新
NullLogger();
var服务=新的LibraryAssetService(上下文,_记录器);
var资产=新图书馆资产
{
Id=40,
份数=20份,
Title=“”,
年份=1992年,
状态=新状态{Id=1},
资产类型=新资产类型{Id=1},
作者=新作者{Id=1},
类别=新类别{Id=2},
照片=新资产照片{Id=1}
};
context.LibraryAssets.Attach(资产);
添加(资产);
SaveChanges();
var实际值=等待服务.GetAsset(40);
断言.Equal(40,实际.Id);
}
}

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

您的代码存在一些问题:

  • 如果您的真实数据库是关系数据库,请避免使用
    UseInMemoryDatabase
    数据库进行测试,因为它不支持关系行为
  • 将排列上下文与动作上下文分开。这意味着,为准备测试、添加测试数据等创建一个新的DataContext,并为SUT创建另一个DataContext(
    LibraryAssetService
    DbContext
    存储数据库中可能不存在的本地数据(内存中),在某些情况下可能会显示假绿色测试
  • 添加资产时,不需要
    附加
    。这可能会在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;
      }
    }
    
    最后,使用一个小助手类为测试准备
    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);
    }
    
    公共类TestDataContextFactory
    {
    公共TestDataContextFactory()
    {
    var builder=new DbContextOptionsBuilder();
    var connection=newsqliteconnection(“数据源=:内存:”);
    connection.Open();
    builder.UseSqlite(连接);
    使用(var ctx=newdatacontext(builder.Options))
    {
    ctx.Database.recreated();
    }
    _options=builder.options;
    }
    私有只读DbContextOptions\u选项;
    public DataContext Create()=>新建DataContext(_选项);
    }
    
    您是否收到任何错误或意外结果?我用你的代码做了一个测试,但是没有重现你的问题。与我们分享一个演示,它可以重现您的问题。使用您的代码后,我收到一个外键限制错误。我尝试添加suppressForeignkeyenforcement,但这仍然无助于创建github回购,我会检查它。代码在我的机器上运行正常。确保您没有使用
    Attach()
    方法。这是指向github repo@lawrencefejokwu的链接,请在
    LibraryAssetService
    的第34行设置断点,并监视您的
    资产
    对象。如您所见,外键值有许多
    0
    值,如
    StatusId
    AssetTypeId
    AuthorId
    。这就是外键限制发生的原因。数据库中没有ID为0的作者!我注释掉了
    LibraryAsset
    中的所有外键,并使用Sqlite进行了测试,结果是绿色的!请确保您使用资产对象所需的任何东西初始化资产对象,然后就可以开始了。@lawrencefejokwu还有一件事:我检查了
    DataContext
    类,您没有配置资产实体。为EF定义实体关系,这样可以使实体更小、更清晰。并且要注意
    OnModelCreating
    方法中的播种。这可能会导致性能问题。
    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);
         }
    
      }
    }
    
    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);
    }