使用Xunit与Moq时无法实例化EntityEntry代理类的问题
单元测试中EntityEntry实例化错误排查与解决
错误信息
System.ArgumentException: '无法实例化类的代理:Microsoft.EntityFrameworkCore.ChangeTracking.EntityEntry`1[[Main.Domain.Contact, PublicKeyToken=null]]。找不到无参数构造函数。(参数 'constructorArguments')'
错误提示指向富领域类Contact,但该类已包含无参和带参两个公共构造函数,且拥有Update方法。尝试过网络上的Mock方案,均未解决问题。
相关代码
Contact类带参构造函数
public Contact( Guid franchiseId, string firstName, string? middleName, string lastName, bool doNotContact, string? emailAddress, IEnumerable<ContactGroupContact>? contactGroupContacts = null, ICollection<ContactNote> contactNotes = null )
Contact类无参构造函数
public Contact() { ContactGroupContacts = new HashSet<ContactGroupContact>(); ContactTags = new HashSet<ContactTag>(); Messages = new HashSet<Message>(); }
Contact类Update方法
public override void Update(Contact entity) { Initialize(this); this.FirstName = entity.FirstName; this.LastName = entity.LastName; this.CalledName = entity.CalledName; this.DoNotContact = entity.DoNotContact; this.EmailAddress = entity.EmailAddress; this.MiddleName = entity.MiddleName; this.MobileNumber = entity.MobileNumber; // 省略其他属性赋值逻辑 }
Contact类还包含ContactNotes和ContactGroup的ICollection属性,推测与阴影属性、级联删除相关。
原单元测试代码
[Fact] public void Update_ShouldCallModifyUpdatedEntity() { //Arrange Contact contact = A.Fake<Contact>(); contact.UniqueId = Guid.NewGuid(); contact.LastModifiedDate = DateTime.MinValue; Contact contactTbu = A.Fake<Contact>(); contactTbu.UniqueId = contact.UniqueId; contactTbu.Address1 = "Test Address"; #region Utils var internalEntityEntry = new InternalEntityEntry( new Mock<IStateManager>().Object, new RuntimeEntityType("T", typeof(Contact), false, null, null, null, ChangeTrackingStrategy.Snapshot, null, false), contact); var optionsBuilder = new DbContextOptionsBuilder<DbContext>(); optionsBuilder.UseInMemoryDatabase("InMemoryDbForTests"); #endregion var _dbContext = new Mock<DbContext>(optionsBuilder.Options); var _dbSet = new Mock<DbSet<Contact>>(); _dbContext.Setup(x => x.Set<Contact>().AsQueryable()).Returns(_dbSet.Object); _dbContext.Setup(x => x.Set<Contact>()).Returns(_dbSet.Object); _dbContext.Setup(x => x.Set<Contact>().Find(contact.UniqueId)).Returns(contact); _dbContext.Setup(x => x.Entry(It.IsAny<Contact>())).Returns(new EntityEntry<Contact>(internalEntityEntry)); _dbContext.Setup(x => x.Entry(contact).CurrentValues.SetValues(contactTbu));//出错位置 _dbSet.Setup(x => x.Find(It.IsAny<Contact>())) .Returns(contact); //Act _sut = new Repository<Contact>(_dbContext.Object); _sut.Update(contact); //Assert _dbContext.Verify(x=>x.Entry(contact)); _dbContext.Verify(x => x.Entry(It.IsAny<Contact>()).CurrentValues.SetValues(contact)); }
问题分析
错误的根源不是Contact类,而是EntityEntry<T>本身没有公开的无参构造函数,且该类的实例化依赖EF Core内部的InternalEntityEntry等组件。手动通过new EntityEntry<Contact>(internalEntityEntry)创建实例后,Moq在处理Mock设置时会尝试为该对象创建代理,由于EntityEntry<T>的构造限制,导致代理创建失败,抛出找不到无参构造函数的错误。
解决方案
通过Moq直接MockEntityEntry<Contact>和PropertyValues对象,避免手动实例化EF内部类,调整后的单元测试代码如下:
[Fact] public void Update_ShouldCallModifyUpdatedEntity() { //Arrange var contact = A.Fake<Contact>(); contact.UniqueId = Guid.NewGuid(); contact.LastModifiedDate = DateTime.MinValue; var contactTbu = A.Fake<Contact>(); contactTbu.UniqueId = contact.UniqueId; contactTbu.Address1 = "Test Address"; // Mock EntityEntry及其CurrentValues属性 var mockEntityEntry = new Mock<EntityEntry<Contact>>(); var mockPropertyValues = new Mock<PropertyValues>(); mockEntityEntry.Setup(entry => entry.CurrentValues).Returns(mockPropertyValues.Object); var optionsBuilder = new DbContextOptionsBuilder<DbContext>(); optionsBuilder.UseInMemoryDatabase("InMemoryDbForTests"); var _dbContext = new Mock<DbContext>(optionsBuilder.Options); var _dbSet = new Mock<DbSet<Contact>>(); // 基础DbSet相关设置 _dbContext.Setup(x => x.Set<Contact>()).Returns(_dbSet.Object); _dbContext.Setup(x => x.Set<Contact>().Find(contact.UniqueId)).Returns(contact); // 让DbContext的Entry方法返回Mock的EntityEntry _dbContext.Setup(x => x.Entry(It.IsAny<Contact>())).Returns(mockEntityEntry.Object); //Act _sut = new Repository<Contact>(_dbContext.Object); _sut.Update(contact); //Assert // 验证Entry方法被调用 _dbContext.Verify(x => x.Entry(contact), Times.Once); // 验证SetValues方法被调用 mockPropertyValues.Verify(values => values.SetValues(contact), Times.Once); }
关键调整点
- 移除手动创建
InternalEntityEntry和EntityEntry<Contact>的代码,改用Moq创建Mock对象,规避EF内部类的构造限制。 - 单独Mock
PropertyValues并关联到EntityEntry的CurrentValues属性,避免直接操作EF内部组件。 - 调整验证逻辑,直接验证
PropertyValues的SetValues方法是否被调用,而非通过DbContext的链式调用验证。
内容的提问来源于stack exchange,提问作者Cenko
相关产品推荐
相关产品推荐

