ASP.NET应用EF Core单元测试ChangeTracker追踪异常问题
问题概述
我有一个ASP.NET应用,想要对其进行单元测试。尽管每次SetUp都会生成新的上下文,但ChangeTracker似乎并未自动清空(手动清空也无效)。
测试代码细节
通用测试初始化
所有测试的通用SetUp方法:
[SetUp] public async Task SetupGeneral() { context = new ContextWrapper(GenerateConnectionString()); context.CreateDatabase(); }
ContextWrapper构造实现
public ContextWrapper(string connString) { context = new Context(connString); ChangeTracker = context.ChangeTracker; }
Context类是继承自DbContext的标准EF Core上下文。
具体测试类的初始化方法
问题出在继承通用测试类的测试类中,其SetUp方法如下:
[SetUp] public async Task SetupPc() { var trackedEntityCount = context.ChangeTracker.Entries().ToList(); psu = CreatePsu(); motherboard = CreateMotherboard(); cpu = CreateCpu(); @case = CreateCase(); cpuCooler = CreateCpuCooler(); fan = CreateFan(); gpu = CreateGpu(); ram = CreateRam(); ssd1 = CreateSsd1(); ssd2 = CreateSsd2(); hdd = CreateHdd(); trackedEntityCount = context.ChangeTracker.Entries().ToList(); await context.SaveChangesAsync().ConfigureAwait(false); }
异常现象
单独运行一个测试时一切正常,但运行多个测试时出现问题:
- 第一个测试:函数开头的
trackedEntityCount为62,创建所有实体后变为79; - 第二个测试:进入函数时开头的
trackedEntityCount是62,创建实体后计数约为111,上下文之间似乎存在冲突。
尝试过以下方案但均无效:在通用TearDown/SetUp中清空ChangeTracker、在该测试的TearDown中清空ChangeTracker、分离所有实体、在测试TearDown中分离特定实体。第一个测试之后的所有测试都会抛出以下异常:
System.InvalidOperationException : The instance of entity type 'Case' cannot be tracked because another instance with the key value '{Id: 3}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached
注:该问题并非仅针对Case实体,调用SaveChangesAsync时任何实体都可能抛出此错误,且Id是自动生成的自增列。
实体生成示例
以下是其中一个实体生成函数示例,其他函数结构类似:
private Psu CreatePsu() { var psu = new Psu() { Name = "Psu1", TotalPower = 650, Output3dot3V = 20, Output5V = 20, Output12Vp = 54, Output12Vm = -0.3, PsuEfficiency = new PsuEfficiency() { Name = "80+ Gold" }, PsuPorts = new HashSet<PsuPort>() { new PsuPort() { PsuPortType = psuPortTypeMB, PortCount = 1 }, new PsuPort() { PsuPortType = psuPortTypeCPU, PortCount = 1 }, new PsuPort() { PsuPortType = psuPortTypePCIe, PortCount = 4 }, new PsuPort() { PsuPortType = psuPortTypeSata, PortCount = 6 }, new PsuPort() { PsuPortType = new PsuPortType { Name = "Molex" }, PortCount = 3 }, } }; context.GetDatabaseSet<Psu>().Add(psu); return psu; }
补充尝试结果
在SetupPc中直接插入ChangeTracker.Clear()后,结果如下:
- 第一个测试:开头的
trackedEntityCount为0,创建实体后为29; - 第二个测试:开头的
trackedEntityCount为0,创建实体后为64。
内容的提问来源于stack exchange,提问作者GrizzlyMedve

