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

xUnit测试中Mock对象在控制器测试失效,返回404问题排查

控制器测试返回404的Mock配置问题解决

问题背景

项目基于通用仓储(Generic Repository)和工作单元(Unit of Work)模式开发API,包含Category表。单独Mock UnitOfWork时功能正常,但测试CategoriesController的GetCategoryById接口时始终返回404状态码,推测是Mock配置错误但无法定位。

核心原因

Moq默认使用引用相等匹配方法参数:测试中Setup时传入的是特定的GetCategoriesWithParentsSpecification(1)实例,但控制器内部会创建一个全新的GetCategoriesWithParentsSpecification(id)实例,两者是不同对象,导致Mock不匹配,返回默认值(null)。后续控制器中直接访问category.Id触发空引用异常,进入catch块返回错误,最终表现为404(实际是异常处理逻辑导致的返回结果偏差)。

解决方案

1. 修正Mock参数匹配逻辑

将测试中的Setup改为匹配任意GetCategoriesWithParentsSpecification实例,或匹配符合业务条件的实例:

方式一:匹配任意该类型实例(简单直接)

_unitOfWork.Setup(x => x.Repository<Category>().GetEntityWithSpec(It.IsAny<GetCategoriesWithParentsSpecification>()))
           .ReturnsAsync(newCategory)
           .Verifiable();

方式二:匹配指定Id的实例(更精确)

如果GetCategoriesWithParentsSpecification类公开了用于筛选的Id属性,可以用条件匹配:

_unitOfWork.Setup(x => x.Repository<Category>().GetEntityWithSpec(It.Is<GetCategoriesWithParentsSpecification>(spec => spec.TargetId == 1)))
           .ReturnsAsync(newCategory)
           .Verifiable();

注:将spec.TargetId替换为实际类中存储筛选Id的属性名

2. 修复控制器空引用风险

在控制器中先判断category是否为null,再检查Id,避免空引用异常:

var category = await _unitOfWork.Repository<Category>().GetEntityWithSpec(spec);

if (category == null || category.Id == 0) 
    return NotFound(new ApiResponse(404));

3. 补充Mapper的Mock配置

测试中需MockIMapper的Map方法,否则返回的CategoryToReturnDto会为null,影响断言:

var categoryDto = new CategoryToReturnDto { Id = newCategory.Id, Name = newCategory.Name };
_mapper.Setup(m => m.Map<Category, CategoryToReturnDto>(newCategory)).Returns(categoryDto);

修正后的完整测试代码

public class CategoriesControllerTests
{
    private readonly Mock<IMapper> _mapper;
    private readonly Mock<IUnitOfWork> _unitOfWork;

    public CategoriesControllerTests()
    {
        _mapper = new Mock<IMapper>();
        _unitOfWork = new Mock<IUnitOfWork>();
    }

    [Fact]
    public async Task Get_OK_ObjectResult_CategoryById()
    {
        //Arrange
        Category newCategory = CreateTestCategory();
        var categoryDto = new CategoryToReturnDto { Id = newCategory.Id, Name = newCategory.Name };

        // 修正Mock参数匹配
        _unitOfWork.Setup(x => x.Repository<Category>().GetEntityWithSpec(It.IsAny<GetCategoriesWithParentsSpecification>()))
                   .ReturnsAsync(newCategory)
                   .Verifiable();

        // Mock Mapper转换
        _mapper.Setup(m => m.Map<Category, CategoryToReturnDto>(newCategory)).Returns(categoryDto);

        var controller = new CategoriesController(_unitOfWork.Object, _mapper.Object);

        //Act
        var result = await controller.GetCategoryById(1);

        // Assert
        var okResult = result.Result as OkObjectResult;
        okResult.ShouldNotBeNull();
        
        var apiResponse = okResult.Value as ApiResponse;
        apiResponse.ShouldNotBeNull();
        apiResponse.StatusCode.ShouldBe(200);
        
        var returnedDto = apiResponse.Data as CategoryToReturnDto;
        returnedDto.ShouldNotBeNull();
        returnedDto.Id.ShouldBe(newCategory.Id);
        returnedDto.Name.ShouldBe(newCategory.Name);
    }

    private Category CreateTestCategory()
    {
        return new Category()
        {
            Id = 1,
            Priority = 1,
            Name = "Test Category",
            Enabled = true,
            Details = "Testing category data"
        };
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 23:40:38