You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在.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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 11:45:31