使用Mock进行API Controller的NUnit测试时遭遇空引用异常与断言失败问题求助
搞定DataPrizeController的NUnit测试问题
看起来你在写NUnit测试时遇到了两个头疼的异常,我先帮你捋清楚问题根源,再给你一套完整的测试代码写法。
问题出在哪?
- 空引用异常(NullReferenceException):你测试类里的
service和_Prize都是类级别的字段,但在Setup方法里完全没初始化,创建控制器实例时传的都是null,调用GetAllPrizes时自然会触发空引用错误。 - 断言失败(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); }
几个额外的优化建议
- 给PrizeService加接口:现在你用的是真实的PrizeService+模拟仓储,其实更好的做法是定义
IPrizeService接口,让控制器依赖接口而非具体类,这样测试时可以直接模拟Service,耦合度更低。 - 优化控制器返回类型:别再返回
Object了,改用IActionResult或者强类型结果(比如ActionResult<IEnumerable<Prize>>),这样测试时能更方便地验证结果,也符合ASP.NET Core的最佳实践。 - 完善异常处理:控制器里的catch块最好加上日志记录,而不是只返回简单的"Error"或布尔值,后续排查问题会更轻松。
内容的提问来源于stack exchange,提问作者Sarwar Akbar
相关产品推荐
相关产品推荐

