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

使用XUnit编写测试时遇Article空引用异常的解决方法

问题原因及解决办法

空引用异常的核心原因有三个:

  1. Mediator实例未注入控制器:测试中创建了mediatorMock,但没有将其赋值给控制器的Mediator属性,导致控制器调用Mediator.Send()时实际是调用null对象的方法,返回null。
  2. Mediator的Setup匹配逻辑失效:使用具体的createArticleCommand对象作为匹配条件,由于引用类型的相等性判断,实际执行时的命令对象和Setup中的对象引用不同,导致Setup不触发,返回null。
  3. UserService的Setup匹配逻辑不匹配控制器实际调用:控制器中调用_userService.GetAsync(article.CreatedBy.ToString(), ...),但测试中Setup的是_currentUserMock.Object.GetUserId().ToString(),两者参数不一致,后续也会引发异常。

具体修复步骤

1. 确保控制器的依赖被正确注入

如果控制器通过构造函数接收依赖,测试时需将mock实例传入构造函数:

// 控制器构造函数示例
public class ArticlesController : ControllerBase
{
    private readonly IMediator _mediator;
    private readonly IUserService _userService;

    public ArticlesController(IMediator mediator, IUserService userService)
    {
        _mediator = mediator;
        _userService = userService;
    }

    // ... 其他代码
}

测试中初始化控制器时传入mock:

// 替换原有控制器初始化代码
var mediatorMock = new Mock<IMediator>();
var userServiceMock = new Mock<IUserService>();
var _articlesController = new ArticlesController(mediatorMock.Object, userServiceMock.Object);

若控制器依赖是public属性,直接赋值即可:

_articlesController.Mediator = mediatorMock.Object;
_articlesController._userService = userServiceMock.Object;

2. 修改Mediator的Setup逻辑

使用It.IsAny<>匹配任意参数,避免引用不匹配问题:

mediatorMock
    .Setup(m => m.Send(It.IsAny<CreateArticleCommand>(), It.IsAny<CancellationToken>()))
    .ReturnsAsync(new ArticleDto 
    { 
        Id = Guid.NewGuid(),
        CreatedBy = Guid.NewGuid() // 必须设置该值,否则后续ToString()会触发空引用
    });

3. 修改UserService的Setup逻辑

匹配控制器实际调用的参数,可选择精确匹配或任意字符串匹配:

var mockArticle = new ArticleDto 
{ 
    Id = Guid.NewGuid(),
    CreatedBy = Guid.NewGuid() 
};
var user = new UserDto { Id = Guid.NewGuid(), Email = "test@test.com", FirstName = "Test", LastName = "User" };

// 先设置Mediator返回预设的mockArticle
mediatorMock
    .Setup(m => m.Send(It.IsAny<CreateArticleCommand>(), It.IsAny<CancellationToken>()))
    .ReturnsAsync(mockArticle);

// 精确匹配CreatedBy的字符串值
userServiceMock
    .Setup(x => x.GetAsync(mockArticle.CreatedBy.ToString(), It.IsAny<CancellationToken>()))
    .ReturnsAsync(user);

完整修复后的测试代码

[Fact]
public async Task CreateAsync_ReturnsValidArticleDto()
{
    // Arrange
    var cancellationToken = CancellationToken.None;
    var controllerContext = new ControllerContext
    {
        HttpContext = _httpContext.Object
    };

    // 初始化mock与控制器
    var mediatorMock = new Mock<IMediator>();
    var userServiceMock = new Mock<IUserService>();
    var _articlesController = new ArticlesController(mediatorMock.Object, userServiceMock.Object);
    _articlesController.ControllerContext = controllerContext;

    var contentTag = new ContentTagDto(1, "test")
    {
        Id = 1,
        Title = "test"
    };
    var contentTagDtoList = new List<ContentTagDto>() { contentTag };
    var createArticleCommand = new CreateArticleCommand("Title", "content", Guid.NewGuid(), ContentItemState.Draft, contentTagDtoList);

    // 准备预设的mock数据
    var mockArticle = new ArticleDto 
    { 
        Id = Guid.NewGuid(),
        CreatedBy = Guid.NewGuid() 
    };
    var user = new UserDto { Id = Guid.NewGuid(), Email = "test@test.com", FirstName = "Test", LastName = "User" };

    // 配置Mediator返回预设对象
    mediatorMock
        .Setup(m => m.Send(It.IsAny<CreateArticleCommand>(), It.IsAny<CancellationToken>()))
        .ReturnsAsync(mockArticle);

    // 配置UserService返回预设用户
    userServiceMock
        .Setup(x => x.GetAsync(mockArticle.CreatedBy.ToString(), It.IsAny<CancellationToken>()))
        .ReturnsAsync(user);

    // Act
    var result = await _articlesController.CreateAsync(cancellationToken, createArticleCommand);

    // Assert
    Assert.NotNull(result);
    Assert.Equal(user, result.CreatedByUser);
    Assert.Equal(user, result.LastModifiedByUser);
}

内容的提问来源于stack exchange,提问作者Wahéb

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 03:09:55