C# 使用DbSet操作对象<;T>;和IQueryable<;T>;with NSubstitute返回错误

C# 使用DbSet操作对象<;T>;和IQueryable<;T>;with NSubstitute返回错误,c#,entity-framework,unit-testing,nsubstitute,dbset,C#,Entity Framework,Unit Testing,Nsubstitute,Dbset,我想通过模拟来对EntityFramework6.x进行单元测试。幸运的是,提供了一个很好的单元测试库,使用。所以,我修改了他的代码,使之适合NSubstitute,到目前为止,它看起来还不错,直到我想测试DbSet.Add(),DbSet.Remove()方法。以下是我的代码: public static class NSubstituteDbSetExtensions { public static DbSet<TEntity> SetupData<TEntity>

我想通过模拟来对EntityFramework6.x进行单元测试。幸运的是,提供了一个很好的单元测试库,使用。所以,我修改了他的代码,使之适合NSubstitute,到目前为止,它看起来还不错,直到我想测试
DbSet.Add()
DbSet.Remove()
方法。以下是我的代码:

public static class NSubstituteDbSetExtensions
{
  public static DbSet<TEntity> SetupData<TEntity>(this DbSet<TEntity> dbset, ICollection<TEntity> data = null, Func<object[], TEntity> find = null) where TEntity : class
  {
    data = data ?? new List<TEntity>();
    find = find ?? (o => null);

    var query = new InMemoryAsyncQueryable<TEntity>(data.AsQueryable());

    ((IQueryable<TEntity>)dbset).Provider.Returns(query.Provider);
    ((IQueryable<TEntity>)dbset).Expression.Returns(query.Expression);
    ((IQueryable<TEntity>)dbset).ElementType.Returns(query.ElementType);
    ((IQueryable<TEntity>)dbset).GetEnumerator().Returns(query.GetEnumerator());

#if !NET40
    ((IDbAsyncEnumerable<TEntity>)dbset).GetAsyncEnumerator().Returns(new InMemoryDbAsyncEnumerator<TEntity>(query.GetEnumerator()));
    ((IQueryable<TEntity>)dbset).Provider.Returns(query.Provider);
#endif

    ...

    dbset.Remove(Arg.Do<TEntity>(entity =>
                                 {
                                   data.Remove(entity);
                                   dbset.SetupData(data, find);
                                 }));

    ...

    dbset.Add(Arg.Do<TEntity>(entity =>
                              {
                                data.Add(entity);
                                dbset.SetupData(data, find);
                              });

    ...

    return dbset;
  }
}
public静态类NSubstituteDbSetExtensions
{
公共静态DbSet SetupData(此DbSet DbSet,ICollection data=null,Func find=null),其中tenty:class
{
数据=数据??新列表();
find=find??(o=>null);
var query=newinmemoryasyncqueryable(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());
#如果!NET40
((IDbAsyncEnumerable)dbset.GetAsyncEnumerator().Returns(新的InMemoryDbAsyncEnumerator(query.GetEnumerator()));
((IQueryable)dbset.Provider.Returns(query.Provider);
#恩迪夫
...
dbset.Remove(Arg.Do(entity=>
{
数据删除(实体);
设置数据(数据,查找);
}));
...
dbset.Add(Arg.Do(entity=>
{
数据。添加(实体);
设置数据(数据,查找);
});
...
返回dbset;
}
}
我创建了一种测试方法,如:

[TestClass]
public class ManipulationTests
{
  [TestMethod]
  public void Can_remove_set()
  {
    var blog = new Blog();
    var data = new List<Blog> { blog };

    var set = Substitute.For<DbSet<Blog>, IQueryable<Blog>, IDbAsyncEnumerable<Blog>>()
                        .SetupData(data);

    set.Remove(blog);

    var result = set.ToList();

    Assert.AreEqual(0, result.Count);
  }
}

public class Blog
{
   ...
}
[TestClass]
公共类操纵测试
{
[测试方法]
public void可以删除集合()
{
var blog=newblog();
var data=新列表{blog};
var set=Substitute.For()
.设置数据(数据);
设置。删除(blog);
var result=set.ToList();
Assert.AreEqual(0,result.Count);
}
}
公共类博客
{
...
}
当测试方法调用
set.Remove(blog)
时会出现问题。它会抛出一个
InvalidOperationException
,错误消息为

集合已修改;枚举操作可能无法执行

这是因为调用
set.Remove(blog)
方法时,已修改了伪
data
对象。但是,原始Scott使用
Moq
的方法不会导致问题

因此,我用一个
try…catch(invalidoOperationException ex)
块包装了
set.Remove(blog)
方法,让
catch
块什么都不做,然后测试就不会抛出异常(当然)并按预期通过

我知道这不是解决方案,但如何实现单元测试的目标,即DbSet.Add()和DbSet.Remove()方法?

这里发生了什么

  • set.Remove(blog);
    -这将调用先前配置的lambda
  • 数据。删除(实体);
    -该项将从列表中删除
  • dbset.SetupData(data,find);
    -我们再次调用SetupData,用新列表重新配置替换项
  • SetupData
    运行
  • 在那里,调用了dbSetup.Remove,以便重新配置下次调用Remove时发生的情况

  • 好的,我们这里有一个问题。
    dtSetup.Remove(Arg.Do@@Frank ah,抱歉。我刚度假回来。:-)它很有魅力!我应该了解更多关于lambda表达式的信息。谢谢!
    
    public static DbSet<TEntity> SetupData<TEntity>(
        this DbSet<TEntity> dbset,
        ICollection<TEntity> data = null,
        Func<object[], TEntity> find = null) where TEntity : class
    {
        data = data ?? new List<TEntity>();
        find = find ?? (o => null);
    
        Func<IQueryable<TEntity>> getQuery = () => new InMemoryAsyncQueryable<TEntity>(data.AsQueryable());
    
        ((IQueryable<TEntity>) dbset).Provider.Returns(info => getQuery().Provider);
        ((IQueryable<TEntity>) dbset).Expression.Returns(info => getQuery().Expression);
        ((IQueryable<TEntity>) dbset).ElementType.Returns(info => getQuery().ElementType);
        ((IQueryable<TEntity>) dbset).GetEnumerator().Returns(info => getQuery().GetEnumerator());
    
    #if !NET40
        ((IDbAsyncEnumerable<TEntity>) dbset).GetAsyncEnumerator()
                                                .Returns(info => new InMemoryDbAsyncEnumerator<TEntity>(getQuery().GetEnumerator()));
        ((IQueryable<TEntity>) dbset).Provider.Returns(info => getQuery().Provider);
    #endif
    
        dbset.Remove(Arg.Do<TEntity>(entity => data.Remove(entity)));
        dbset.Add(Arg.Do<TEntity>(entity => data.Add(entity)));
    
        return dbset;
    }
    
    // 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);