仓储模式下用真实数据库测试:是单元测试还是集成测试?
1. 使用真实数据库的测试属于单元测试还是集成测试?
这类测试属于集成测试。单元测试的核心是隔离被测组件,仅验证其自身逻辑,不依赖外部资源(如数据库、网络服务)。而你的测试需要验证仓储与数据库的交互逻辑(比如ExecuteDeleteAsync的执行结果、AsNoTracking的行为),这涉及到仓储和数据库两个组件的协作,完全符合集成测试的定义。
如果要对仓储做单元测试,应该只验证其中不依赖数据库的逻辑(比如DeleteUrlAsync中缓存移除的分支逻辑),这部分可以通过Mock IMemoryCache和DbContext的基础接口来实现,但数据库相关的执行逻辑必须通过集成测试验证。
2. 无法Mock或用内存数据库测试是否说明仓储模式使用错误?
不是错误。仓储模式的核心价值是将业务层与具体的数据访问框架(EF Core)解耦,让业务层无需关注EF Core的细节,而不是要求仓储完全脱离EF Core的特性。
你在仓储中使用ExecuteDeleteAsync、AsNoTracking等EF Core特定功能是合理的——仓储本来就是用来封装数据访问逻辑的,包括框架提供的高效操作。无法Mock这些方法是因为它们是EF Core的扩展方法或底层实现,Mock这类方法没有实际意义,你需要验证的是这些操作的真实效果,而非模拟的假逻辑。
测试方案建议
TestContainers
完全适合用来测试仓储类。它能创建与生产环境一致的真实数据库实例,避免内存数据库或轻量数据库与真实数据库的行为差异(比如EF Core特性支持、SQL语法兼容性),确保测试结果的可靠性。虽然启动容器有一定开销,但对于验证数据访问逻辑的正确性来说,这个成本是值得的。
SQLite替代方案
作为轻量快速的测试方案,可以使用SQLite内存数据库:测试前应用迁移,测试完成后销毁数据库。但需要注意SQLite与生产数据库的行为差异,比如部分EF Core特性的支持程度不同,可能导致测试通过但生产环境出现问题,适合快速迭代的小规模场景。
存在测试问题的仓储代码示例
public class UrlRepository(AppDbContext dbContext, IMemoryCache memoryCache) : IUrlRepository { private readonly AppDbContext _dbContext = dbContext; private readonly IMemoryCache _memoryCache = memoryCache; public virtual async Task<int> DeleteUrlAsync(Guid urlId) { int rows = await _dbContext.Urls .Where(u => u.Id == urlId) .ExecuteDeleteAsync(); // Cannot mock it and cannot use InMemory provider since exception will be thrown if (rows > 0) { _memoryCache.Remove($"{CacheKeys.UrlId}-{urlId}"); } return rows; } public virtual async Task<PaginationDto<UrlDto>> GetAllUrlDtoAsync(int pageNumber, int pageSize) { var query = _dbContext.Urls.AsNoTracking(); // Cannot mock it since it is an extension method var totalCount = await query.CountAsync(); var totalPages = (int)Math.Ceiling((double)totalCount / pageSize); var urls = await query .Skip((pageNumber - 1) * pageSize) .Take(pageSize) .Select(UrlEntityProjection.UrlDto) .ToListAsync(); return urls.ToPagination(u => u, totalCount, totalPages, pageNumber, pageSize); } }
内容的提问来源于stack exchange,提问作者Quikler

