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

编写控制器Xunit测试时遇‘对象引用未设置到对象实例’错误求助

解决Xunit测试AccountsController时的“对象引用未设置到对象实例”错误

我来帮你搞定这个空引用问题!这个错误十有八九是你在初始化控制器依赖项的时候没做好——比如userManager、jwtFactory这些参数,要么是传了null,要么是内部的关键属性没配置,导致控制器执行Register方法时调用到了空对象的属性/方法。

下面是具体的排查和解决步骤:

1. 检查并正确初始化所有依赖项

控制器的构造函数依赖的四个参数(context、userManager、jwtFactory、jwtoptions)都必须是可用的实例,不能直接传null或者未配置的对象:

模拟DbContext(context)

用内存数据库来模拟你的DbContext,这样既不会影响真实数据库,也能保证上下文实例可用:

var options = new DbContextOptionsBuilder<YourDbContext>()
    .UseInMemoryDatabase(databaseName: "TestRegistrationDb")
    .Options;
var context = new YourDbContext(options);

正确模拟UserManager

UserManager不能直接new,它依赖多个接口,最好用Moq框架来模拟:

// 先模拟IUserStore
var userStoreMock = new Mock<IUserStore<ApplicationUser>>();
// 创建UserManager实例(后面的null可以根据需要替换为模拟的依赖)
var userManager = new UserManager<ApplicationUser>(
    userStoreMock.Object,
    null, null, null, null, null, null, null, null
);

// 如果需要模拟UserManager的具体方法(比如CreateAsync),可以加Setup
var userManagerMock = new Mock<UserManager<ApplicationUser>>(
    Mock.Of<IUserStore<ApplicationUser>>(), null, null, null, null, null, null, null, null
);
userManagerMock.Setup(x => x.CreateAsync(It.IsAny<ApplicationUser>(), It.IsAny<string>()))
               .ReturnsAsync(IdentityResult.Success);

初始化JwtOptions

jwtoptions是IOptions<JwtOptions>类型,要用Options.Create来创建实例,并且配置必要的属性:

var jwtOptions = Options.Create(new JwtOptions
{
    SecretKey = "your-test-secret-key-123",
    Issuer = "test-issuer",
    Audience = "test-audience",
    ExpiryMinutes = 30
});

处理JwtFactory

如果jwtFactory是你自定义的类,要确保它的所有依赖(比如Logger、其他服务)都被正确初始化或者模拟。比如它依赖ILogger<JwtFactory>,就用Moq模拟:

var loggerMock = new Mock<ILogger<JwtFactory>>();
var jwtFactory = new JwtFactory(jwtOptions, loggerMock.Object);

2. 完善控制器的上下文配置

有时候控制器的ControllerContext未初始化也会导致空引用,建议在new控制器后加上这行:

ctrl.ControllerContext = new ControllerContext
{
    HttpContext = new DefaultHttpContext()
};

3. 检查ViewModel和ModelState的问题

你给ModelState加了错误,但要确保RegistrationViewModel的必填属性都有值(比如注册通常需要密码,你的示例里只给了Email,控制器代码可能会尝试访问Password属性导致空引用?)。如果控制器里会校验ViewModel的完整性,记得给ViewModel补全必要属性:

var mod = new RegistrationViewModel 
{ 
    Email = "johnmiller@sins.com",
    Password = "TestPass123!",
    ConfirmPassword = "TestPass123!"
};

最后,完整的测试示例片段

把这些整合起来,你的测试代码大概会变成这样:

[Fact]
public async Task RegistrationTest()
{
    // 初始化DbContext
    var dbOptions = new DbContextOptionsBuilder<YourDbContext>()
        .UseInMemoryDatabase("TestDb")
        .Options;
    var context = new YourDbContext(dbOptions);

    // 模拟UserManager
    var userStoreMock = new Mock<IUserStore<ApplicationUser>>();
    var userManager = new UserManager<ApplicationUser>(userStoreMock.Object, null, null, null, null, null, null, null, null);

    // 初始化JwtOptions
    var jwtOptions = Options.Create(new JwtOptions
    {
        SecretKey = "test-secret",
        Issuer = "test-issuer"
    });

    // 模拟JwtFactory(假设它依赖IOptions和ILogger)
    var loggerMock = new Mock<ILogger<JwtFactory>>();
    var jwtFactory = new JwtFactory(jwtOptions, loggerMock.Object);

    // 创建控制器并配置上下文
    var ctrl = new AccountsControllers(context, userManager, jwtFactory, jwtOptions);
    ctrl.ControllerContext = new ControllerContext { HttpContext = new DefaultHttpContext() };

    // 添加ModelState错误(如果需要测试错误分支)
    ctrl.ModelState.AddModelError("x", "Model error");

    // 完善ViewModel
    var mod = new RegistrationViewModel 
    { 
        Email = "johnmiller@sins.com",
        Password = "Test123!"
    };

    // 执行测试
    IActionResult result = await ctrl.Register(mod);

    // 你的断言逻辑
    // Assert.Equal(mod.Email, ...);
}

按照这个思路调整你的测试代码,应该就能解决空引用的问题了!

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.26 10:42:03