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

单元测试CreateWorkspaceHandler时Moq调用验证失败求助

单元测试验证数据库调用失败:预期调用一次但实际0次

Handler代码

....
namespace SchemaModeler.Host.Modules;
........
public class CreateWorkspaceHandler : IRequestHandler<CreateWorkspaceInput, Workspace?>
{
    private readonly IDbContextFactory<AppDbContext> _dbContextFactory;
    private readonly ITopicEventSender _eventSender;

    public async ValueTask<Workspace?> Handle(
        CreateWorkspaceInput request,
        CancellationToken cancellationToken)
    {
        await using AppDbContext dbContext =
            await _dbContextFactory.CreateDbContextAsync(cancellationToken);

        if (string.IsNullOrEmpty(request.Name))
        {
            throw new InvalidNameException(request.Name);
        }

        Organization organization =
            await dbContext.Organizations
                .FirstOrDefaultAsync(x => x.Id == request.OrganizationId, cancellationToken)
            ?? throw new EntityNotFoundException(request.OrganizationId, nameof(Organization)); 
       .................
        var workspace = new Workspace
        {
           .......
            Organization = organization,
           ........
        };

       .......
        await _eventSender.SendAsync(nameof(WorkspaceSubscriptions.OnCreatedWorkspace), workspace,
            cancellationToken);
        return workspace;
    }
}

测试代码

....
namespace SchemaModeler.Host.Tests.UnitTests;
....
[UsesVerify]
[Collection("Postgres")]
public class WorkspaceTests : TestBase
{
    public override async Task InitializeAsync()
    {
        await base.InitializeAsync();  // Call the base InitializeAsync() method
        await using AppDbContext context = CreateDbContext();
        context.Workspaces.Add(WorkspaceMockFixture.Workspaces);
        await context.SaveChangesAsync();
    }
    [Fact]
    public async Task Handle_InvalidName_ThrowsInvalidNameException()
    {
        var organizationId = new Guid();
       .........
        var mockOrganizationsDbSet = new Mock<DbSet<Organization>>();
        mockOrganizationsDbSet.Setup(m => m.FindAsync(It.IsAny<object[]>()))
                              .ReturnsAsync((Organization)null);
        MockDbContext.Setup(dbContext => dbContext.Set<Organization>())
                     .Returns(mockOrganizationsDbSet.Object);
        ..........    
        MockDbContext.Verify(
            dbContext => dbContext.Set<Organization>().FindAsync(
                It.IsAny<object[]>(),
                CancellationToken
            ),
            Times.Once
        );  // 此处报错
        ..........
    }
}

运行测试抛出的异常

Moq.MockException : Expected invocation on the mock once, but was 0
times: dbContext => dbContext.Set< Organization >().FindAsync(It.IsAny<object[]>(),
CancellationToken)

Performed invocations:

MockAppDbContext:1 (dbContext): No invocations performed.

问题分析与解决思路

  • 执行流程逻辑冲突:Handler中优先检查request.Name是否为空,一旦为空直接抛出InvalidNameException,后续查询Organization的代码根本不会执行。当前测试用例的场景是验证无效名称抛出异常,此时数据库查询逻辑未触发,所以Verify断言必然失败。
  • 错误的验证方向:该测试用例的核心目标是验证InvalidNameException的抛出,无需验证数据库调用。应删除Verify代码,替换为异常断言,例如:
    await Assert.ThrowsAsync<InvalidNameException>(async () => 
        await handler.Handle(new CreateWorkspaceInput { Name = null, OrganizationId = organizationId }, CancellationToken.None)
    );
    
  • 方法不匹配问题:即使走到数据库查询逻辑,Handler中使用的是FirstOrDefaultAsync,但测试中Verify的是FindAsync,两者方法不一致,断言仍会失败。需保持方法匹配:要么修改Handler使用FindAsync,要么调整测试VerifyFirstOrDefaultAsync的调用。
  • 拆分测试场景:若要验证数据库调用,需单独编写测试用例,传入有效的Name参数,确保代码走到查询Organization的逻辑分支。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 21:15:16