首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >NSubstitute DbSet / IQueryable<T>

NSubstitute DbSet / IQueryable<T>
EN

Stack Overflow用户
提问于 2014-01-12 00:57:11
回答 5查看 20.4K关注 0票数 41

因此,EntityFramework 6比以前的版本具有更好的测试性。互联网上也有类似于Moq框架的一些很好的例子,但情况是,我更喜欢使用NSubstitute。我已经翻译了使用NSubstitute的“非查询”示例,但是我无法理解‘查询-测试’。

Moq的items.As<IQueryable<T>>().Setup(m => m.Provider).Returns(data.Provider);是如何转换成NSubstitute的?我想类似((IQueryable<T>) items).Provider.Returns(data.Provider);的东西,但那是行不通的。我也尝试过items.AsQueryable().Provider.Returns(data.Provider);,但这也不起作用。

我得到的启示是:

"System.NotImplementedException :成员'IQueryable.Provider‘尚未在'DbSet1Proxy' which inherits from 'DbSet1’类型上实现。‘DbSet’1‘的测试加倍必须提供所使用的方法和属性的实现。“

让我引用上面链接中的代码示例。此代码示例使用Moq来模拟DbContext和DbSet。

代码语言:javascript
复制
public void GetAllBlogs_orders_by_name()
{
  // Arrange
  var data = new List<Blog>
  {
     new Blog { Name = "BBB" },
     new Blog { Name = "ZZZ" },
     new Blog { Name = "AAA" },
  }.AsQueryable();

  var mockSet = new Mock<DbSet<Blog>>();
  mockSet.As<IQueryable<Blog>>().Setup(m => m.Provider).Returns(data.Provider);
  mockSet.As<IQueryable<Blog>>().Setup(m => m.Expression).Returns(data.Expression);
  mockSet.As<IQueryable<Blog>>().Setup(m => m.ElementType).Returns(data.ElementType);
  mockSet.As<IQueryable<Blog>>().Setup(m => m.GetEnumerator()).Returns(data.GetEnumerator());

  var mockContext = new Mock<BloggingContext>();
  mockContext.Setup(c => c.Blogs).Returns(mockSet.Object);

  // ...
}

这就是我和NSubstitute一起走的这么远

代码语言:javascript
复制
public void GetAllBlogs_orders_by_name()
{
  // Arrange
  var data = new List<Blog>
  {
     new Blog { Name = "BBB" },
     new Blog { Name = "ZZZ" },
     new Blog { Name = "AAA" },
  }.AsQueryable();

  var mockSet = Substitute.For<DbSet<Blog>>();
  // it's the next four lines I don't get to work
  ((IQueryable<Blog>) mockSet).Provider.Returns(data.Provider);
  ((IQueryable<Blog>) mockSet).Expression.Returns(data.Expression);
  ((IQueryable<Blog>) mockSet).ElementType.Returns(data.ElementType);
  ((IQueryable<Blog>) mockSet).GetEnumerator().Returns(data.GetEnumerator());

  var mockContext = Substitute.For<BloggingContext>();
  mockContext.Blogs.Returns(mockSet);

  // ...
}

因此,问题是:如何替代IQueryable (如提供者)的属性?

EN

回答 5

Stack Overflow用户

回答已采纳

发布于 2014-01-12 14:06:23

这是因为NSubstitute语法特定。例如:

代码语言:javascript
复制
((IQueryable<Blog>) mockSet).Provider.Returns(data.Provider);

NSubstitute调用提供程序的getter,然后指定返回值。这个getter调用不会被替代程序拦截,您将得到一个异常。这是因为IQueryable.Provider属性在DbQuery类中的显式实现。

您可以使用NSub显式地创建多个接口的替代品,并创建一个覆盖所有指定接口的代理。然后,对接口的调用将被替代程序拦截。请使用以下语法:

代码语言:javascript
复制
// Create a substitute for DbSet and IQueryable types:
var mockSet = Substitute.For<DbSet<Blog>, IQueryable<Blog>>();
    
// And then as you do:
((IQueryable<Blog>) mockSet).Provider.Returns(data.Provider);
((IQueryable<Blog>) mockSet).Expression.Returns(data.Expression);
((IQueryable<Blog>) mockSet).ElementType.Returns(data.ElementType);
((IQueryable<Blog>) mockSet).GetEnumerator().Returns(data.GetEnumerator());
票数 42
EN

Stack Overflow用户

发布于 2014-01-12 12:34:41

多亏了Kevin,我在代码翻译中发现了问题。

单元代码样本正在模拟DbSet,但是NSubstitute需要接口实现。因此,与用于NSubstitute的Moqs NSubstitute等价的是Substitute.For<IDbSet<Blog>>()。您并不总是需要提供接口,所以这就是我感到困惑的原因。但在这一具体案例中,事实证明,这是至关重要的。

结果还表明,在使用接口IDbSet时,我们不必强制转换为Queryable。

因此,工作测试代码:

代码语言:javascript
复制
public void GetAllBlogs_orders_by_name()
{
  // Arrange
  var data = new List<Blog>
  {
    new Blog { Name = "BBB" },
    new Blog { Name = "ZZZ" },
    new Blog { Name = "AAA" },
  }.AsQueryable();

  var mockSet = Substitute.For<IDbSet<Blog>>();
  mockSet.Provider.Returns(data.Provider);
  mockSet.Expression.Returns(data.Expression);
  mockSet.ElementType.Returns(data.ElementType);
  mockSet.GetEnumerator().Returns(data.GetEnumerator());

  var mockContext = Substitute.For<BloggingContext>();
  mockContext.Blogs.Returns(mockSet);

  // Act and Assert ...
}

我编写了一个小的扩展方法来清理单元测试的排列部分。

代码语言:javascript
复制
public static class ExtentionMethods
{
    public static IDbSet<T> Initialize<T>(this IDbSet<T> dbSet, IQueryable<T> data) where T : class
    {
        dbSet.Provider.Returns(data.Provider);
        dbSet.Expression.Returns(data.Expression);
        dbSet.ElementType.Returns(data.ElementType);
        dbSet.GetEnumerator().Returns(data.GetEnumerator());
        return dbSet;
    }
}

// usage like:
var mockSet = Substitute.For<IDbSet<Blog>>().Initialize(data);

这不是问题,但如果您还需要支持异步操作的话:

代码语言:javascript
复制
public static IDbSet<T> Initialize<T>(this IDbSet<T> dbSet, IQueryable<T> data) where T : class
{
  dbSet.Provider.Returns(data.Provider);
  dbSet.Expression.Returns(data.Expression);
  dbSet.ElementType.Returns(data.ElementType);
  dbSet.GetEnumerator().Returns(data.GetEnumerator());

  if (dbSet is IDbAsyncEnumerable)
  {
    ((IDbAsyncEnumerable<T>) dbSet).GetAsyncEnumerator()
      .Returns(new TestDbAsyncEnumerator<T>(data.GetEnumerator()));
    dbSet.Provider.Returns(new TestDbAsyncQueryProvider<T>(data.Provider));
  }

  return dbSet;
}

// create substitution with async
var mockSet = Substitute.For<IDbSet<Blog>, IDbAsyncEnumerable<Blog>>().Initialize(data);
// create substitution without async
var mockSet = Substitute.For<IDbSet<Blog>>().Initialize(data);
票数 19
EN

Stack Overflow用户

发布于 2015-08-27 11:25:11

这是我生成假DbSet的静态通用静态方法。它可能会有用。

代码语言:javascript
复制
 public static class CustomTestUtils
{
    public static DbSet<T> FakeDbSet<T>(List<T> data) where T : class
    {
        var _data = data.AsQueryable();
        var fakeDbSet = Substitute.For<DbSet<T>, IQueryable<T>>();
        ((IQueryable<T>)fakeDbSet).Provider.Returns(_data.Provider);
        ((IQueryable<T>)fakeDbSet).Expression.Returns(_data.Expression);
        ((IQueryable<T>)fakeDbSet).ElementType.Returns(_data.ElementType);
        ((IQueryable<T>)fakeDbSet).GetEnumerator().Returns(_data.GetEnumerator());

        fakeDbSet.AsNoTracking().Returns(fakeDbSet);

        return fakeDbSet;
    }

}
票数 7
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/21069986

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档