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

如何在C# XUnit中验证非Mock方法是否被调用

验证player.ConfirmPlayer()调用的测试方案

因为Player对象是从EF Core的DbSet中查询得到的,没有被注入或Mock,无法直接用Moq验证方法调用,以下是三种可行的测试方案:

方案一:验证Player的状态变更(推荐,符合行为驱动测试逻辑)

ConfirmPlayer()的核心作用是将玩家标记为已确认状态,与其验证方法是否被调用,不如直接验证调用后的业务状态是否符合预期——这更贴合测试的实际意义。

测试步骤示例(xUnit + Moq):

[Fact]
public async Task Handle_ShouldMarkPlayerAsConfirmed()
{
    // 准备测试数据
    var testPlayerId = Guid.NewGuid();
    var testPlayer = new Player { Id = testPlayerId, IsConfirmed = false };
    
    var mockDbSet = new Mock<DbSet<Player>>();
    mockDbSet.Setup(m => m.FirstAsync(It.IsAny<Expression<Func<Player, bool>>>(), default))
             .ReturnsAsync(testPlayer);
    
    var mockContext = new Mock<YourDbContext>();
    mockContext.Setup(c => c.Players).Returns(mockDbSet.Object);
    
    var handler = new ConfirmPlayerCommandHandler(mockContext.Object);
    var request = new ConfirmPlayerCommand { PlayerId = testPlayerId };

    // 执行目标方法
    await handler.Handle(request, CancellationToken.None);

    // 验证状态变更(间接证明ConfirmPlayer被调用)
    Assert.True(testPlayer.IsConfirmed);
    mockContext.Verify(c => c.SaveChangesAsync(default), Times.Once);
}

方案二:创建可Mock的Player子类(针对必须验证方法调用的场景)

如果业务要求必须严格验证ConfirmPlayer()方法的调用行为,可以修改Player类的方法可继承性,通过Mock子类来实现验证:

  1. 先修改Player类,将ConfirmPlayer()标记为virtual:
public class Player
{
    public Guid Id { get; set; }
    public bool IsConfirmed { get; private set; }

    public virtual void ConfirmPlayer()
    {
        IsConfirmed = true;
    }
}
  1. 编写测试代码验证方法调用:
[Fact]
public async Task Handle_ShouldCallConfirmPlayer()
{
    // 准备Mock的Player实例
    var testPlayerId = Guid.NewGuid();
    var mockPlayer = new Mock<Player>();
    mockPlayer.Setup(p => p.Id).Returns(testPlayerId);
    
    var mockDbSet = new Mock<DbSet<Player>>();
    mockDbSet.Setup(m => m.FirstAsync(It.IsAny<Expression<Func<Player, bool>>>(), default))
             .ReturnsAsync(mockPlayer.Object);
    
    var mockContext = new Mock<YourDbContext>();
    mockContext.Setup(c => c.Players).Returns(mockDbSet.Object);
    
    var handler = new ConfirmPlayerCommandHandler(mockContext.Object);
    var request = new ConfirmPlayerCommand { PlayerId = testPlayerId };

    // 执行目标方法
    await handler.Handle(request, CancellationToken.None);

    // 直接验证方法调用
    mockPlayer.Verify(p => p.ConfirmPlayer(), Times.Once);
    mockContext.Verify(c => c.SaveChangesAsync(default), Times.Once);
}

方案三:使用EF Core内存数据库(集成测试场景)

如果希望测试更贴近生产环境的真实运行逻辑,可以用EF Core的内存数据库替代Mock,直接操作真实实体对象:

[Fact]
public async Task Handle_ShouldConfirmPlayer_InMemoryDb()
{
    // 配置内存数据库
    var options = new DbContextOptionsBuilder<YourDbContext>()
        .UseInMemoryDatabase(databaseName: "Test_ConfirmPlayer")
        .Options;
    
    using var context = new YourDbContext(options);
    var testPlayer = new Player { Id = Guid.NewGuid(), IsConfirmed = false };
    context.Players.Add(testPlayer);
    await context.SaveChangesAsync();
    
    var handler = new ConfirmPlayerCommandHandler(context);
    var request = new ConfirmPlayerCommand { PlayerId = testPlayer.Id };

    // 执行目标方法
    await handler.Handle(request, CancellationToken.None);

    // 验证数据库中的状态变更
    var updatedPlayer = await context.Players.FirstAsync(p => p.Id == testPlayer.Id);
    Assert.True(updatedPlayer.IsConfirmed);
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 11:52:23