使用带有NSubstitute的DbSet 和IQueryable 操作对象会返回错误
我想使用NSubstitute通过模拟DbSet对Entity Framework 6.x进行unit testing。 幸运的是, Scott Xu使用Moq提供了一个很好的unit testing库EntityFramework.Testing.Moq 。 因此,我修改了他的代码以适合NSubstitute并且它一直看起来很好,直到我想测试DbSet.Add()
, DbSet.Remove()
方法。 这是我的代码位:
public static class NSubstituteDbSetExtensions { public static DbSet SetupData(this DbSet dbset, ICollection data = null, Func find = null) where TEntity : class { data = data ?? new List(); find = find ?? (o => null); var query = new InMemoryAsyncQueryable(data.AsQueryable()); ((IQueryable)dbset).Provider.Returns(query.Provider); ((IQueryable)dbset).Expression.Returns(query.Expression); ((IQueryable)dbset).ElementType.Returns(query.ElementType); ((IQueryable)dbset).GetEnumerator().Returns(query.GetEnumerator()); #if !NET40 ((IDbAsyncEnumerable)dbset).GetAsyncEnumerator().Returns(new InMemoryDbAsyncEnumerator(query.GetEnumerator())); ((IQueryable)dbset).Provider.Returns(query.Provider); #endif ... dbset.Remove(Arg.Do(entity => { data.Remove(entity); dbset.SetupData(data, find); })); ... dbset.Add(Arg.Do(entity => { data.Add(entity); dbset.SetupData(data, find); }); ... return dbset; } }
我创建了一个测试方法,如:
[TestClass] public class ManipulationTests { [TestMethod] public void Can_remove_set() { var blog = new Blog(); var data = new List { blog }; var set = Substitute.For<DbSet, IQueryable, IDbAsyncEnumerable>() .SetupData(data); set.Remove(blog); var result = set.ToList(); Assert.AreEqual(0, result.Count); } } public class Blog { ... }
当测试方法调用set.Remove(blog)
时会出现问题。 它抛出一个InvalidOperationException
,错误消息为
collections被修改; 枚举操作可能无法执行。
这是因为在调用set.Remove(blog)
方法时修改了伪data
对象。 然而,原始Scott使用Moq
的方式不会导致问题。
因此,我用一个try ... catch (InvalidOperationException ex)
块包装了set.Remove(blog)
方法,让catch
块什么都不做,然后测试不会抛出exception(当然)并且确实传递为预期。
我知道这不是解决方案,但是如何实现unit testingDbSet.Add()
和DbSet.Remove()
方法的目标?
这里发生了什么事?
-
set.Remove(blog);
– 这会调用先前配置的lambda。 -
data.Remove(entity);
– 该项目已从列表中删除。 -
dbset.SetupData(data, find);
– 我们再次调用SetupData,用新列表重新配置Substitute。 -
SetupData
运行… - 在那里,正在调用
dbSetup.Remove
,以便重新配置下次调用Remove时会发生什么。
好的,我们这里有问题。 dtSetup.Remove(Arg.Do
这导致了结论:当我们的一个模拟动作正在运行时,我们无法修改替代品的作用。 如果你考虑一下,没有人读你的测试会认为这发生了,所以你根本不应该这样做。
我们该如何解决?
public static DbSet SetupData ( this DbSet dbset, ICollection data = null, Func (); find = find ?? (o => null); Func> getQuery = () => new InMemoryAsyncQueryable (data.AsQueryable()); ((IQueryable ) dbset).Provider.Returns(info => getQuery().Provider); ((IQueryable ) dbset).Expression.Returns(info => getQuery().Expression); ((IQueryable ) dbset).ElementType.Returns(info => getQuery().ElementType); ((IQueryable ) dbset).GetEnumerator().Returns(info => getQuery().GetEnumerator()); #if !NET40 ((IDbAsyncEnumerable ) dbset).GetAsyncEnumerator() .Returns(info => new InMemoryDbAsyncEnumerator (getQuery().GetEnumerator())); ((IQueryable ) dbset).Provider.Returns(info => getQuery().Provider); #endif dbset.Remove(Arg.Do (entity => data.Remove(entity))); dbset.Add(Arg.Do (entity => data.Add(entity))); return dbset; }
-
getQuery
lambda创建一个新查询。 它始终使用捕获的列表data
。 - 所有
.Returns
配置调用都使用lambda。 在那里,我们创建一个新的查询实例并在那里委托我们的调用。 -
Remove
和Add
仅修改我们捕获的列表。 我们不必重新配置替换,因为每个调用都使用lambda表达式重新评估查询。
虽然我非常喜欢NSubstitute,但我强烈建议您查看entity frameworkunit testing工具Effort 。
你会像这样使用它:
// DbContext needs additional constructor: public class MyDbContext : DbContext { public MyDbContext(DbConnection connection) : base(connection, true) { } } // Usage: DbConnection connection = Effort.DbConnectionFactory.CreateTransient(); MyDbContext context = new MyDbContext(connection);
在那里你有一个实际的DbContext,您可以使用Entity Framework为您提供的所有内容,包括迁移,使用快速的内存数据库。
- .net-core-2.0 azure app service 502.5错误
- 如何删除dbo.AspNetUserClaims和dbo.AspNetUserLogins表(IdentityUserClaim和IdentityUserLogin实体)?
- 启动应用程序并将其发送到第二台显示器?
- Unity中的c#中的属性(Unity5) – 可以避免支持变量
- 如何使我的WinForms应用程序DPI意识
- 使用c#中的复杂元素进行Xml反序列化
- 循环访问TabControl中的控件
- C#FileSystemWatcher WaitForChanged方法仅检测一个文件更改
- .NET模拟.NETFramework 2 VS .NETFrameWork 4中的ASP.NET模拟