使用Moq编写C#更新联系人单元测试时遇空引用异常求助
我用Moq框架编写单元测试验证UpdateContact功能,运行测试时抛出NullReferenceException,推测是待修改对象的列表未被正确赋值。我对Moq和单元测试并不熟悉,最初配置Contact.Get方法返回测试数据,之后修改为配置Where(c => c.Id == contact.Id,null,null).FirstOrDefault()返回对应数据,但问题仍存在,想了解具体错误原因。
测试代码
[TestFixture] public class UpdateContactTests { private static readonly object[] TestValidContact = { new object[] { "Prueba", "nearlinx@gmail.com", "86456245", new LookUpItem() { LookUpItemId = Guid.NewGuid(), Category = "ContactType", CategoryId = 1, Value = "Bulling Contact" }, new Company() { Id = Guid.NewGuid(), Name = "Company", Email = "company@gmail.com", WebSite = "company.com", Nda = false, Msa = false, Phone = "84876817" } } }; [TestCaseSource(nameof(TestValidContact))] [Test] public void UpdateContact_ValidValues_UpdateContact(string name, string email, string phone, LookUpItem lookUpItem, Company company) { //arrange var id = Guid.NewGuid(); var data = new[] { new Contact { Id = Guid.NewGuid(), Name = "Test1", Email = "nearlinx@gmail.com", Phone = "86456245", ContactType = new LookUpItem() { LookUpItemId = Guid.NewGuid(), Category = "ContactType", CategoryId = 1, Value = "Bulling Contact" }, Company = new Company() { Id = Guid.NewGuid(), Name = "Company", Email = "company@gmail.com", WebSite = "company.com", Nda = false, Msa = false, Phone = "84876817" }}, new Contact { Id = id, Name = "Test2", Email = "nearlinx@gmail.com", Phone = "86456245", ContactType = new LookUpItem() { LookUpItemId = Guid.NewGuid(), Category = "ContactType", CategoryId = 1, Value = "Bulling Contact" }, Company = new Company() { Id = Guid.NewGuid(), Name = "Company", Email = "company@gmail.com", WebSite = "company.com", Nda = false, Msa = false, Phone = "84876817" }}, }; var contact = new Contact() { Id = id, Name = name, Email = email, Phone = phone, ContactType = lookUpItem, Company = company }; var _unitOfWork = new Mock<IUnitOfWork>(); // 最初的配置 //_unitOfWork.Setup(mock => mock.Contact.Get(null, null, null)).Returns(data); // 修改后的配置 _unitOfWork.Setup(mock => mock.Contact.Where(c => c.Id == contact.Id,null,null).FirstOrDefault()).Returns(() => data.Where(c => c.Id == contact.Id)); _unitOfWork.Setup(mock => mock.Company.Get(null, null, null)).Returns(new List<Company>()); _unitOfWork.Setup(mock => mock.LookUpItem.Get(null, null, null)).Returns(new List<LookUpItem>()); var contactService = new ContactService(_unitOfWork.Object); //act contactService.UpdateContact(contact); //assert Assert.That(data.First(m => m.Id == id).Name, Is.EqualTo(contact.Name)); _unitOfWork.Verify(mock => mock.Contact.Update(It.IsAny<Contact>()), Times.Once); } }
错误原因解析
Moq Setup匹配失败:你硬编码了
Where方法的lambda表达式作为匹配条件,但Moq默认会严格匹配参数(包括lambda的引用实例),而ContactService实际调用时用的lambda和你Setup里的不是同一个,导致Setup不生效,返回null触发NullReferenceException。返回值类型不匹配:
data.Where(c => c.Id == contact.Id)返回的是IEnumerable<Contact>集合,但FirstOrDefault()预期返回单个Contact对象,类型不匹配,即使Setup命中也无法拿到正确对象。断言逻辑错误:你直接断言本地
data数组的对象属性被修改,但ContactService操作的是Moq模拟的仓储对象,不会影响你本地的data数组,这个断言本身不成立。
修正方案
1. 正确配置Moq的Where方法
用It.IsAny<>匹配参数,让Where方法能根据传入的过滤条件返回正确集合:
// 替换原来的Where Setup _unitOfWork.Setup(mock => mock.Contact.Where(It.IsAny<Expression<Func<Contact, bool>>>(), null, null)) .Returns<Expression<Func<Contact, bool>>, object, object>((filterExpr, _, __) => data.Where(filterExpr.Compile()) );
2. 优化断言逻辑
放弃修改本地data的断言,转而验证仓储的Update方法被正确调用且传入对象符合预期:
// 替换原来的Assert部分 _unitOfWork.Verify(mock => mock.Contact.Update(It.Is<Contact>(c => c.Id == contact.Id && c.Name == contact.Name && c.Email == contact.Email && c.Phone == contact.Phone )), Times.Once);
3. 优先使用GetById方法(如果存在)
如果你的IContactRepository提供了GetById方法,直接Setup会更简单:
// 替换Where的Setup _unitOfWork.Setup(mock => mock.Contact.GetById(contact.Id)) .Returns(data.First(c => c.Id == contact.Id));
内容的提问来源于stack exchange,提问作者TK154

