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

ASP.NET Core 6中Command Handler单元测试问题(无需仓储模式)

问题:ASP.NET Core 6模拟DbContext时抛出NotSupportedException

在为CreateQueryCommandHandler编写单元测试时,使用Moq模拟QueryStoreDbContext的Add方法时触发System.NotSupportedException,错误提示不可重写的成员(QueryStoreDbContext.get_Queries)不能用于设置/验证表达式,要求在不使用仓储模式的前提下解决该问题。

错误信息

System.NotSupportedException: Unsupported expression: x => x.Queries
Non-overridable members (here: QueryStoreDbContext.get_Queries) may not be used in setup / verification expressions.
   at Moq.Guard.IsOverridable(MethodInfo method, Expression expression) in /_/src/Moq/Guard.cs:line 99
   at Moq.ExpressionExtensions.<Split>g__Split|5_0(Expression e, Expression& r, MethodExpectation& p, Boolean assignment, Boolean allowNonOverridableLastProperty) in /_/src/Moq/ExpressionExtensions.cs:line 191
   at Moq.ExpressionExtensions.Split(LambdaExpression expression, Boolean allowNonOverridableLastProperty) in /_/src/Moq/ExpressionExtensions.cs:line 170
   at Moq.Mock.SetupRecursive[TSetup](Mock mock, LambdaExpression expression, Func`4 setupLast, Boolean allowNonOverridableLastProperty) in /_/src/Moq/Mock.cs:line 728
   at Moq.Mock.Setup(Mock mock, LambdaExpression expression, Condition condition) in /_/src/Moq/Mock.cs:line 562
   at Moq.Mock`1.Setup[TResult](Expression`1 expression) in /_/src/Moq/Mock`1.cs:line 645
   at QueryStore.Application.UnitTests.Queries.Commands.CreateQueryCommandHandlerTests.Handle_Should_ReturnSuccessResult_WhenQueryIsAddedToDatabase() in Application/tests/Queries/Commands/CreateQueryCommandHandlerTests.cs:line 96

原测试代码(问题片段)

[Fact]
public async Task Handle_Should_ReturnSuccessResult_WhenQueryIsAddedToDatabase()
{
    var command = new CreateQueryCommand()
    {
        Name = "Some Name",
        Content = "Select * from AbcCompany",
        Description = "Some description about the query.",
        Tags = new[] { "company", "import" }
    };
    
    // 这里触发错误:尝试模拟非虚的Queries属性
    _contextMock.Setup(x => x.Queries.Add(It.IsAny<Query>()));

    var handler = new CreateQueryCommandHandler(_contextMock.Object, _validator, _loggerMock.Object);

    var validationResult = await _validator.ValidateAsync(command);
    var result = await handler.Handle(command, CancellationToken.None);

    _contextMock.Verify(x => x.Queries.Add(It.IsAny<Query>()), Times.Once);
    _contextMock.Verify(x => x.SaveChangesAsync(CancellationToken.None), Times.Once);

    Assert.True(validationResult.IsValid);
    Assert.IsType<Result<int>>(result);
    Assert.True(result.IsSuccess);
    Assert.True(result.Value > 0);
}

处理器核心代码

try
{
    var entity = request.AsQuery();
    // 实际调用的是DbContext的Add方法,而非DbSet的Add
    _context.Add(entity);
    await _context.SaveChangesAsync(cancellationToken);
    return Result<int>.Success(entity.Id);
}

解决方案

核心思路

EF Core自动生成的DbSet属性默认是非虚(non-virtual)的,Moq无法模拟这类成员。但观察处理器代码,实际调用的是DbContext基类的Add<TEntity>方法而非Queries.Add,因此直接模拟DbContext的Add和SaveChangesAsync方法即可,无需触碰Queries属性。

修改后的测试代码

[Fact]
public async Task Handle_Should_ReturnSuccessResult_WhenQueryIsAddedToDatabase()
{
    var command = new CreateQueryCommand()
    {
        Name = "Some Name",
        Content = "Select * from AbcCompany",
        Description = "Some description about the query.",
        Tags = new[] { "company", "import" }
    };

    // 捕获添加的Query实体,用于后续设置Id
    Query addedQuery = null;
    _contextMock.Setup(x => x.Add(It.IsAny<Query>()))
                .Callback<Query>(query => addedQuery = query);

    // 模拟SaveChangesAsync返回成功,并设置实体Id为预期值
    _contextMock.Setup(x => x.SaveChangesAsync(It.IsAny<CancellationToken>()))
                .ReturnsAsync(1)
                .Callback(() => addedQuery.Id = 1);

    var handler = new CreateQueryCommandHandler(_contextMock.Object, _validator, _loggerMock.Object);

    var validationResult = await _validator.ValidateAsync(command);
    var result = await handler.Handle(command, CancellationToken.None);

    // 验证方法调用次数
    _contextMock.Verify(x => x.Add(It.IsAny<Query>()), Times.Once);
    _contextMock.Verify(x => x.SaveChangesAsync(CancellationToken.None), Times.Once);

    // 断言结果
    Assert.True(validationResult.IsValid);
    Assert.IsType<Result<int>>(result);
    Assert.True(result.IsSuccess);
    Assert.Equal(1, result.Value);
}

可选优化(针对后续扩展)

如果后续测试需要模拟DbSet的查询操作,可以将DbContext中的DbSet属性改为虚(virtual):

public class QueryStoreDbContext : DbContext
{
    // 将DbSet标记为virtual,允许Moq模拟
    public virtual DbSet<Query> Queries { get; set; }

    // ...其他上下文配置代码
}

但针对当前处理器的逻辑,此优化非必需。

内容的提问来源于stack exchange,提问作者Hardik Dhuri

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 03:54:54