单元测试执行时出现“Key has already been added”错误的排查求助
问题分析与解决方案
错误原因
- InMemory数据库数据冲突:你使用了固定名称的InMemory数据库
"iSMS",每个测试方法执行前都会运行Initialize方法,第二次执行时尝试添加主键Adrid=1的重复实体,导致主键冲突抛出异常。 - 错误Mock测试目标类:你Mock了
AddressService本身,但单元测试的核心是验证这个类的真实业务逻辑,应该直接实例化该类,而非通过Mock对象调用方法(Mock对象默认返回默认值,无法覆盖真实逻辑的验证需求)。
修复步骤
1. 为每个测试创建独立的InMemory数据库
将数据库名称改为动态生成的唯一值(比如Guid),确保每个测试的数据库完全隔离,避免数据交叉污染:
var options = new DbContextOptionsBuilder<iSMSContext>() .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) .Options;
2. 直接实例化AddressService而非Mock
删除_addressMock字段,改为直接创建AddressService的真实实例:
private AddressService _addressService; // 在Initialize方法中替换Mock为真实实例 _addressService = new AddressService(_mockCache.Object, _context, _mockLogger.Object);
3. 修改测试方法中的调用对象
将测试方法中_addressMock.Object的调用替换为_addressService。
完整修复后的测试代码
public class AddressServiceTest { private Mock<IAppCache> _mockCache; private Mock<ILogger<AddressService>> _mockLogger; private iSMSContext _context; private AddressService _addressService; [TestInitialize] public void Initialize() { _mockCache = new Mock<IAppCache>(); _mockLogger = new Mock<ILogger<AddressService>>(); // 生成唯一名称的InMemory数据库 var options = new DbContextOptionsBuilder<iSMSContext>() .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) .Options; _context = new iSMSContext(options); _context.Adresses.Add(new Adresse { Adrid = 1, Projid = 1, Lfd = 1, Sortid = 1, Aquelle = 1, Istfirma = 1, Titel = "TestTitle", Anrede = "TestAnrede", Vorname = "TestVorname", Strhnr = "TestStreet 123", Plz = "12345", Ort = "TestCity", Land = "TestLand", }); _context.SaveChanges(); // 直接实例化待测试的服务类 _addressService = new AddressService(_mockCache.Object, _context, _mockLogger.Object); } [TestMethod] public void GetStreetAndHouseNumber_Test() { string street = "TestStreet"; string houseNumber = "123"; int lfd = 1; int projid = 1; var address = _addressService.GetStreetAndHouseNumber(street, houseNumber, lfd, projid); Assert.IsNotNull(address); Assert.AreEqual($"{street} {houseNumber}", address); } [TestMethod] public void GetPostalCode_Test() { string postalCode = "12345"; int lfd = 1; int projid = 1; var result = _addressService.GetPostalCode(postalCode, lfd, projid); Assert.IsNotNull(result); Assert.AreEqual(result, postalCode); } }
额外建议
- 可以在
TestCleanup方法中添加_context.Dispose();,测试完成后销毁DbContext实例,避免不必要的资源占用。 - 如果
AddressService依赖的其他服务(比如IAppCache)需要特定行为,可以通过_mockCache.Setup(...)配置模拟逻辑,确保测试环境完全可控。
内容的提问来源于stack exchange,提问作者Sascha Manns
相关产品推荐
相关产品推荐

