ASP.NET MVC项目单元测试遇System.NullReferenceException问题求助
问题排查与解决方案
核心问题分析
- 空引用异常根源:测试类中使用
new SaltWaterTankMetricRepository()无参构造函数实例化仓储,而该构造函数并未初始化_dbContext字段。当调用Create方法执行_dbContext.Add(metrics)时,_dbContext为null,直接抛出NullReferenceException。 - 断言逻辑错误:
Create方法返回的是metrics.MetricsID(int类型),但测试中用result.Should().Be(2022-11-8, "78.0", "8.3", ".003", "8.2"),参数类型和数量完全不匹配,即使解决空引用问题,断言也会失败。
修复方案
方案1:使用EF内存数据库进行集成测试
这种方式可以模拟真实的EF操作,无需依赖真实数据库,适合验证仓储逻辑的正确性:
修改测试类代码:
namespace SaltWaterTankMetrics.DataAccess.EF.test { public class SaltWaterTankMetricTest { private readonly SaltWaterTankMetricRepository _sut; private readonly SaltwaterTankContext _dbContext; public SaltWaterTankMetricTest() { // 配置内存数据库 var options = new DbContextOptionsBuilder<SaltwaterTankContext>() .UseInMemoryDatabase(databaseName: "TestTankDb") .Options; _dbContext = new SaltwaterTankContext(options); _sut = new SaltWaterTankMetricRepository(_dbContext); } [Theory] [InlineData("2022-11-8", 78.0, 8.3, .003, 8.2)] public void CreateMetric_ShouldPass(DateTime date, decimal temp, decimal ph, decimal nh3, decimal dkh) { // Arrange var createMetrics = new SaltWaterTankMetric { MetricsDate = date, SaltWaterTemp = temp, SaltWaterPH = ph, SaltWaterNH3 = nh3, SaltWaterDKH = dkh }; // Act var resultId = _sut.Create(createMetrics); var createdMetric = _dbContext.SaltWaterTankMetrics.FirstOrDefault(m => m.MetricsID == resultId); // Assert resultId.Should().BeGreaterThan(0); // 验证ID生成正常 createdMetric.Should().NotBeNull(); createdMetric.MetricsDate.Should().Be(date); createdMetric.SaltWaterTemp.Should().Be(temp); createdMetric.SaltWaterPH.Should().Be(ph); createdMetric.SaltWaterNH3.Should().Be(nh3); createdMetric.SaltWaterDKH.Should().Be(dkh); } } }
方案2:使用Mock框架进行纯单元测试
如果只想测试仓储逻辑,不依赖EF实现,可以用Moq框架Mock DbContext和DbSet:
首先确保安装Moq包,然后修改测试类:
using Moq; using Microsoft.EntityFrameworkCore; namespace SaltWaterTankMetrics.DataAccess.EF.test { public class SaltWaterTankMetricTest { private readonly SaltWaterTankMetricRepository _sut; private readonly Mock<SaltwaterTankContext> _mockContext; public SaltWaterTankMetricTest() { _mockContext = new Mock<SaltwaterTankContext>(); // Mock DbSet var mockSet = new Mock<DbSet<SaltWaterTankMetric>>(); _mockContext.Setup(c => c.SaltWaterTankMetrics).Returns(mockSet.Object); // 模拟Add和SaveChanges行为 _mockContext.Setup(c => c.Add(It.IsAny<SaltWaterTankMetric>())) .Callback<SaltWaterTankMetric>(m => m.MetricsID = 1); // 模拟ID赋值 _mockContext.Setup(c => c.SaveChanges()).Returns(1); _sut = new SaltWaterTankMetricRepository(_mockContext.Object); } [Theory] [InlineData("2022-11-8", 78.0, 8.3, .003, 8.2)] public void CreateMetric_ShouldPass(DateTime date, decimal temp, decimal ph, decimal nh3, decimal dkh) { var createMetrics = new SaltWaterTankMetric { MetricsDate = date, SaltWaterTemp = temp, SaltWaterPH = ph, SaltWaterNH3 = nh3, SaltWaterDKH = dkh }; var resultId = _sut.Create(createMetrics); resultId.Should().Be(1); // 验证Mock返回的ID _mockContext.Verify(c => c.Add(createMetrics), Times.Once); _mockContext.Verify(c => c.SaveChanges(), Times.Once); } } }
额外说明
- 原项目运行正常是因为实际环境中会通过依赖注入(DI)给仓储传入已初始化的
SaltwaterTankContext实例,而测试中没有这个注入过程,导致上下文为空。 - 建议移除无参构造函数,强制通过构造函数注入上下文,从根源避免此类空引用问题。
内容的提问来源于stack exchange,提问作者Austin Poythress
相关产品推荐
相关产品推荐

