如何在.NET中为Linq表达式编写NUnit单元测试用例
NUnit单元测试编写方案
核心测试思路
你要测试的Query方法逻辑非常薄,本身不包含自定义筛选逻辑,核心作用是将传入的Linq谓词透传给底层_dbSet的Where方法执行,因此测试不需要覆盖.Where本身的实现(这是.NET官方库的已验证能力),只需要验证两点:
- 传入的谓词确实被用于数据集筛选
- 方法返回的结果集和谓词筛选后的预期结果完全一致
测试全程要和真实数据库隔离,用内存数据集模拟_dbSet即可,保证测试执行速度和稳定性。
推荐实现方式
优先选择EF Core自带的InMemory内存数据库提供程序构造测试上下文,相比直接Mock DbSet的方式,这种方案更贴近真实运行时的EF Core行为,不会出现Mock配置和实际逻辑脱节的问题。
第一步:准备测试基础设施
首先定义测试用的实体和测试专用DbContext,每个测试用例使用独立的内存数据库实例,避免数据交叉污染:
// 业务实体示例 public class City { public int Id { get; set; } public string CityName { get; set; } public int ProvinceId { get; set; } public bool IsCapital { get; set; } } // 测试专用DbContext public class TestAppDbContext : DbContext { public DbSet<City> Cities { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { // 用随机Guid作为数据库名,保证每个上下文实例对应独立内存库 optionsBuilder.UseInMemoryDatabase(Guid.NewGuid().ToString()); } } // Query方法所在的仓储类参考实现 public class CityRepository { private readonly DbSet<City> _dbSet; public CityRepository(DbContext dbContext) { _dbSet = dbContext.Set<City>(); } public IQueryable<City> Query(Expression<Func<City, bool>> predicate) { return _dbSet.Where(predicate); } }
第二步:编写NUnit测试用例
覆盖4类典型场景即可,不需要穷举所有谓词写法:
- 谓词匹配部分数据的常规场景
- 谓词无任何匹配数据的场景
- 谓词匹配全部数据的场景
- 针对布尔属性直接筛选的场景
[TestFixture] public class CityRepositoryTests { private TestAppDbContext _dbContext; private CityRepository _repository; [SetUp] public void Setup() { _dbContext = new TestAppDbContext(); // 插入固定测试种子数据 _dbContext.Cities.AddRange(new List<City> { new City { Id = 1, CityName = "北京", ProvinceId = 1, IsCapital = true }, new City { Id = 2, CityName = "上海", ProvinceId = 2, IsCapital = false }, new City { Id = 3, CityName = "广州", ProvinceId = 3, IsCapital = false }, new City { Id = 4, CityName = "深圳", ProvinceId = 3, IsCapital = false } }); _dbContext.SaveChanges(); _repository = new CityRepository(_dbContext); } [TearDown] public void TearDown() { _dbContext.Dispose(); } [Test] public void Query_PredicateMatchesPartialData_ReturnsCorrectFilteredSet() { // Arrange Expression<Func<City, bool>> predicate = c => c.ProvinceId == 3; // Act var result = _repository.Query(predicate).ToList(); // Assert Assert.That(result.Count, Is.EqualTo(2)); Assert.That(result.Select(c => c.Id), Is.EquivalentTo(new[] {3, 4})); } [Test] public void Query_PredicateMatchesNoData_ReturnsEmptySet() { // Arrange Expression<Func<City, bool>> predicate = c => c.Id > 100; // Act var result = _repository.Query(predicate).ToList(); // Assert Assert.That(result, Is.Empty); } [Test] public void Query_PredicateMatchesAllData_ReturnsFullSet() { // Arrange Expression<Func<City, bool>> predicate = c => c.Id > 0; // Act var result = _repository.Query(predicate).ToList(); // Assert Assert.That(result.Count, Is.EqualTo(4)); } [Test] public void Query_BooleanPropertyPredicate_ReturnsCorrectMatch() { // Arrange Expression<Func<City, bool>> predicate = c => c.IsCapital; // Act var result = _repository.Query(predicate).ToList(); // Assert Assert.That(result.Count, Is.EqualTo(1)); Assert.That(result.First().CityName, Is.EqualTo("北京")); } }
避坑提示
- 不要直接用Mock框架伪造
_dbSet.Where的返回值,这种写法本质是在验证你自己写的Mock配置,根本没有覆盖Query方法的实际透传逻辑,测试没有意义 - 调用
Query方法后必须调用ToList()、ToArray()等立即执行方法,IQueryable是延迟执行的,如果不触发执行,根本不会实际运行筛选逻辑,测试等于白写 - 不需要在这个方法的测试里验证复杂谓词的筛选正确性,那是EF Core框架的测试覆盖范围,你只需要确认谓词能被正常传递执行即可
内容的提问来源于stack exchange,提问作者karthik
相关产品推荐
相关产品推荐

