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

使用Mock进行API Controller的NUnit测试时遭遇空引用异常与断言失败问题求助

搞定DataPrizeController的NUnit测试问题

看起来你在写NUnit测试时遇到了两个头疼的异常,我先帮你捋清楚问题根源,再给你一套完整的测试代码写法。

问题出在哪?

  1. 空引用异常(NullReferenceException):你测试类里的service和_Prize都是类级别的字段,但在Setup方法里完全没初始化,创建控制器实例时传的都是null,调用GetAllPrizes时自然会触发空引用错误。
  2. 断言失败(AssertionException):因为上面的空引用,控制器直接进入catch块返回了字符串"Error",但你却把Mock的仓储对象和这个返回值做对比,类型和值都对不上,断言肯定失败。

正确的测试代码怎么写?

我们要用Moq来模拟依赖项,正确初始化控制器的所有依赖,然后针对每个接口方法编写对应的测试用例。

先搭好测试类的基础框架

首先,不要在测试类里提前声明未初始化的字段,而是在Setup方法里统一创建模拟对象、实例化服务和控制器:

using Moq;
using NUnit.Framework;
// 记得引入你的控制器、服务和仓储所在的命名空间

namespace nUnitTest
{
    public class DataPrizeControllerTests
    {
        private Mock<IRepository<Prize>> _mockPrizeRepo;
        private PrizeService _prizeService;
        private DataPrizeController _controller;

        [SetUp]
        public void Setup()
        {
            // 1. 创建模拟的仓储实例
            _mockPrizeRepo = new Mock<IRepository<Prize>>();
            // 2. 用模拟仓储初始化真实的PrizeService
            _prizeService = new PrizeService(_mockPrizeRepo.Object);
            // 3. 用真实服务和模拟仓储初始化控制器
            _controller = new DataPrizeController(_prizeService, _mockPrizeRepo.Object);
        }
    }
}

测试GetAllPrizes方法

这个方法要覆盖两种场景:服务正常返回数据、服务抛出异常。

// 接上面的测试类,添加以下测试方法
[Test]
public void GetAllPrizes_WhenServiceReturnsData_ReturnsExpectedPrizes()
{
    // 准备测试用的奖品数据
    var testPrizeList = new List<Prize>
    {
        new Prize { Id = 1, Name = "一等奖" },
        new Prize { Id = 2, Name = "二等奖" }
    };

    // 设置模拟仓储的GetAll方法返回我们准备好的测试数据
    _mockPrizeRepo.Setup(repo => repo.GetAll()).Returns(testPrizeList);

    // 调用控制器的GetAllPrizes方法
    var result = _controller.GetAllPrizes();

    // 断言返回结果和我们的测试数据一致
    Assert.That(result, Is.EqualTo(testPrizeList));
    // 验证模拟仓储的GetAll方法确实被调用了一次
    _mockPrizeRepo.Verify(repo => repo.GetAll(), Times.Once);
}

[Test]
public void GetAllPrizes_WhenServiceThrowsException_ReturnsErrorString()
{
    // 设置模拟仓储的GetAll方法抛出异常
    _mockPrizeRepo.Setup(repo => repo.GetAll()).Throws(new Exception("模拟异常"));

    // 调用控制器方法
    var result = _controller.GetAllPrizes();

    // 断言返回的是"Error"字符串
    Assert.That(result, Is.EqualTo("Error"));
}

测试AddPrize方法

同样覆盖两种场景:添加成功、添加失败(抛出异常)。

// 继续在测试类里添加以下方法
[Test]
public void AddPrize_WhenAddSucceeds_ReturnsTrue()
{
    // 准备要添加的测试奖品
    var testPrize = new Prize { Id = 3, Name = "三等奖" };

    // 设置模拟仓储的Add方法(假设是void类型的方法)
    _mockPrizeRepo.Setup(repo => repo.Add(testPrize));

    // 调用控制器的AddPrize方法
    var result = _controller.AddPrize(testPrize);

    // 断言返回结果为true
    Assert.That(result, Is.True);
    // 验证模拟仓储的Add方法被调用了一次,且参数正确
    _mockPrizeRepo.Verify(repo => repo.Add(testPrize), Times.Once);
}

[Test]
public void AddPrize_WhenAddThrowsException_ReturnsFalse()
{
    var testPrize = new Prize { Id = 3, Name = "三等奖" };

    // 设置模拟仓储的Add方法抛出异常
    _mockPrizeRepo.Setup(repo => repo.Add(testPrize)).Throws(new Exception("添加失败"));

    var result = _controller.AddPrize(testPrize);

    // 断言返回结果为false
    Assert.That(result, Is.False);
}

几个额外的优化建议

  1. 给PrizeService加接口:现在你用的是真实的PrizeService+模拟仓储,其实更好的做法是定义IPrizeService接口,让控制器依赖接口而非具体类,这样测试时可以直接模拟Service,耦合度更低。
  2. 优化控制器返回类型:别再返回Object了,改用IActionResult或者强类型结果(比如ActionResult<IEnumerable<Prize>>),这样测试时能更方便地验证结果,也符合ASP.NET Core的最佳实践。
  3. 完善异常处理:控制器里的catch块最好加上日志记录,而不是只返回简单的"Error"或布尔值,后续排查问题会更轻松。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.27 20:47:35