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
相关产品推荐
相关产品推荐

