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

xUnit与Moq测试问题:Mock返回Task<string>但response.Value为空

排查xUnit.net+Moq测试中Controller返回值为空的问题

在使用xUnit.net和Moq对CabinetController进行测试时,已为控制器模拟了所有依赖项,并设置Mock的ValidateWearerAsync方法返回Task<string>,但测试中response.Value始终为空,需要排查原因。

测试代码

namespace TestProject
{
    public class CabinetControllerTest : ControllerBase
    {
        private Mock<IWearerPort>    _canOpenPort;
        private Mock<ITwoTraceLogger>  _twoTraceLogger;
        private Mock<ITransactionPort> _transaction;

        public CabinetControllerTest()
        {
            _canOpenPort = new Mock<IWearerPort>();
            _transaction = new Mock<ITransactionPort>();
            _twoTraceLogger = new Mock<ITwoTraceLogger>();
        }

        [Fact]
        public async void AccessControl_Success_Test()
        {
            //Arrange
            Task<string> task = Task.FromResult<string>("Forbidden");
            _canOpenPort.Setup(c => 
            c.ValidateWearerAsync("BorrowCabinet", 
            "643674")).Returns(task);
            var controller = new 
            CabinetController(_transaction.Object, 
            _canOpenPort.Object, _twoTraceLogger.Object);
            var expected = Ok("Forbidden");

            //Act
            var response = await 
            controller.AccessControl("BorrowCabinet", "643674");

            var actual = Ok(response.Value);

            //Assert
            Assert.Equal(expected, actual);
            _canOpenPort.Verify(c => 
            c.ValidateWearerAsync("BorrowCabinet", "643674"), 
            Times.Once);
        }
    }
}

控制器代码

[ApiController]
[Route("[controller]")]
public class CabinetController : ControllerBase
{
    private readonly IWearerPort _canOpenPort;
    private readonly ITwoTraceLogger _twoTraceLogger;
    private readonly ITransactionPort _transaction;

    public CabinetController(ITransactionPort transaction, 
    IWearerPort canOpen, ITwoTraceLogger twoTraceLogger)
    {
        _twoTraceLogger = twoTraceLogger;
        _canOpenPort = canOpen;
        _transaction = transaction;
    }

    [HttpGet]
    [Route("[controller]/accesscontrol")]
    public async Task<ActionResult<string>> AccessControl(string 
     clientID, string cardId)
    {
        _twoTraceLogger.LogTrace("CabinetController gets called");
        var result = await 
        _canOpenPort.ValidateWearerAsync(clientID,cardId);
        return Ok(result);
    }
}

问题排查与修复

1. 测试方法返回类型错误

xUnit中异步测试方法必须返回async Task,而非async void。async void会导致测试框架无法等待异步操作完成,可能在controller.AccessControl执行完毕前就结束测试,进而导致response.Value未被正确赋值。

修改测试方法的返回类型:

[Fact]
public async Task AccessControl_Success_Test() // 将void改为Task
{
    // ... 其余代码保持不变
}

2. 断言方式错误

直接用Assert.Equal(expected, actual)比较两个OkObjectResult对象是无效的——每次调用Ok()都会创建新的对象实例,引用地址不同,断言必然失败。正确做法是验证返回结果的类型,再取出Value进行比较:

修改断言部分:

// Act
var response = await controller.AccessControl("BorrowCabinet", "643674");

// Assert
var okResult = Assert.IsType<OkObjectResult>(response.Result);
var actualValue = Assert.IsType<string>(okResult.Value);
Assert.Equal("Forbidden", actualValue);
_canOpenPort.Verify(c => c.ValidateWearerAsync("BorrowCabinet", "643674"), Times.Once);

3. 可选优化:简化Mock返回值设置

可以用Moq的ReturnsAsync方法简化异步返回值的设置,无需手动创建Task:

_canOpenPort.Setup(c => c.ValidateWearerAsync("BorrowCabinet", "643674"))
            .ReturnsAsync("Forbidden");

完成以上修改后,测试即可正确获取返回值并通过断言。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 05:43:14