使用具有预加载功能的内存数据库对 EF Core 进行单元测试 [英] Unit testing EF Core using in-memory database with an eager-loaded function
问题描述
我正在为我的 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:
- 如果您的真实数据库是关系数据库,请避免使用
UseInMemoryDatabase
数据库进行测试,因为它不支持关系行为. - 将Arrange 上下文与Act 上下文分开.这意味着,为准备测试、添加测试数据等创建一个新的 DataContext,并为 SUT 创建另一个 DataContext(在本例中为
LibraryAssetService
).DbContext
存储本地数据(在内存中),这些数据可能不存在于数据库中,并且在某些情况下可能会显示虚假的绿色测试! - 添加资产时不需要
Attach
.这可能会使用 sqlite 创建Foreign key constraint
错误.
- If your real databse is relational avoid using
UseInMemoryDatabase
database for testing because it doesn't support relational behaviours. - 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! - You don't need
Attach
when you're adding the assets. That could createForeign 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屋!