单元测试使用内存中的.ToListAsync()

问题描述:

下面是一种失败的测试类型.ShouldNotThrow()由于.ToListAsync()不被内存中的数据库集支持(我没有确切的措辞方便,但你得到的图片)。如果它是重要的,我试图模拟由Entity Framework ver提供的dbset。 6.1.3:单元测试使用内存中的.ToListAsync()

[TestFixture] 
public class Tests 
{ 
    private SomeRepository _repository; 
    private Mock<DbSet<SomeEntity>> _mockDbSet; 
    private Mock<IApplicationDbContext> _mockAppDbContext; 

    [OneTimeSetUp] 
    public void TestFixtureSetUp() 
    { 
     _mockDbSet = new Mock<DbSet<SomeEntity>>(); 
     _mockAppDbContext = new Mock<IApplicationDbContext>(); 
     _mockAppDbContext.SetupGet(c => c.Gigs).Returns(_mockGigsDbSet.Object); 

     _repository = new SomeRepository(_mockAppDbContext.Object); 
    } 

    [Test] 
    public void Test() 
    { 
     // Setup 
     var results = (IEnumerable<SomeEntity>) null; 
     var singleEntity = new SomeEntity {Id = "1"}; 
     _mockDbSet.SetSource(new List<SomeEntity> { singleEntity }); 

     // Act 
     var action = new Func<Task>(async() => 
     { 
      results = await _repository.GetMultipleAsync(); //this ends up calling "await mockDbSet.ToListAsync().ConfigureAwait(false)" internally 
     }); 

     // Verify 
     action.ShouldNotThrow(); //an exception is thrown about .ToListAsync() not being supported by in-memory dbsets or something to that effect 
     results.Should().BeEmpty(); 
    } 
} 

上述试验作品如预期如果.ToList()来代替基于异步-.ToListAsync的同步()使用。此外,从实际的asp.net中使用时,存储库工作正常。

那么嘲讽dbset for .ToListAsync()在这些单元测试中工作的正确方法是什么?

PS:这个项目我一直单元测试可以在这里找到:

 https://bitbucket.org/dsidirop/gighub 

的单元测试,由于未能.ToListAsync()都标有注释“失败暂且”。

+3

有很多箍环可以完全模拟EF DbContext。只有链接的答案是不被接受的,但是这是在手机上输入的大量信息,所以我只是将其留作评论。 [模拟EF DbContext](https://msdn.microsoft.com/en-us/library/dn314429(v = vs.113).aspx)。 –

脱帽向布拉德福德狄龙提供正确答案:

https://msdn.microsoft.com/en-us/library/dn314429(v=vs.113).aspx

的方式适当的方式来创建存储库是先创建这些公用事业样机类异步方法单元测试:

using System.Collections.Generic; 
using System.Data.Entity.Infrastructure; 
using System.Linq; 
using System.Linq.Expressions; 
using System.Threading; 
using System.Threading.Tasks; 

namespace TestingDemo 
{ 
    internal class TestDbAsyncQueryProvider<TEntity> : IDbAsyncQueryProvider 
    { 
     private readonly IQueryProvider _inner; 

     internal TestDbAsyncQueryProvider(IQueryProvider inner) 
     { 
      _inner = inner; 
     } 

     public IQueryable CreateQuery(Expression expression) 
     { 
      return new TestDbAsyncEnumerable<TEntity>(expression); 
     } 

     public IQueryable<TElement> CreateQuery<TElement>(Expression expression) 
     { 
      return new TestDbAsyncEnumerable<TElement>(expression); 
     } 

     public object Execute(Expression expression) 
     { 
      return _inner.Execute(expression); 
     } 

     public TResult Execute<TResult>(Expression expression) 
     { 
      return _inner.Execute<TResult>(expression); 
     } 

     public Task<object> ExecuteAsync(Expression expression, CancellationToken cancellationToken) 
     { 
      return Task.FromResult(Execute(expression)); 
     } 

     public Task<TResult> ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken) 
     { 
      return Task.FromResult(Execute<TResult>(expression)); 
     } 
    } 

    internal class TestDbAsyncEnumerable<T> : EnumerableQuery<T>, IDbAsyncEnumerable<T>, IQueryable<T> 
    { 
     public TestDbAsyncEnumerable(IEnumerable<T> enumerable) 
      : base(enumerable) 
     { } 

     public TestDbAsyncEnumerable(Expression expression) 
      : base(expression) 
     { } 

     public IDbAsyncEnumerator<T> GetAsyncEnumerator() 
     { 
      return new TestDbAsyncEnumerator<T>(this.AsEnumerable().GetEnumerator()); 
     } 

     IDbAsyncEnumerator IDbAsyncEnumerable.GetAsyncEnumerator() 
     { 
      return GetAsyncEnumerator(); 
     } 

     IQueryProvider IQueryable.Provider 
     { 
      get { return new TestDbAsyncQueryProvider<T>(this); } 
     } 
    } 

    internal class TestDbAsyncEnumerator<T> : IDbAsyncEnumerator<T> 
    { 
     private readonly IEnumerator<T> _inner; 

     public TestDbAsyncEnumerator(IEnumerator<T> inner) 
     { 
      _inner = inner; 
     } 

     public void Dispose() 
     { 
      _inner.Dispose(); 
     } 

     public Task<bool> MoveNextAsync(CancellationToken cancellationToken) 
     { 
      return Task.FromResult(_inner.MoveNext()); 
     } 

     public T Current 
     { 
      get { return _inner.Current; } 
     } 

     object IDbAsyncEnumerator.Current 
     { 
      get { return Current; } 
     } 
    } 
} 

,然后用它们像这样:

using Microsoft.VisualStudio.TestTools.UnitTesting; 
using Moq; 
using System.Collections.Generic; 
using System.Data.Entity; 
using System.Data.Entity.Infrastructure; 
using System.Linq; 
using System.Threading.Tasks; 

namespace TestingDemo 
{ 
    [TestClass] 
    public class AsyncQueryTests 
    { 
     [TestMethod] 
     public async Task GetAllBlogsAsync_orders_by_name() 
     { 

      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<IDbAsyncEnumerable<Blog>>() 
       .Setup(m => m.GetAsyncEnumerator()) 
       .Returns(new TestDbAsyncEnumerator<Blog>(data.GetEnumerator())); 

      mockSet.As<IQueryable<Blog>>() 
       .Setup(m => m.Provider) 
       .Returns(new TestDbAsyncQueryProvider<Blog>(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); 

      var service = new BlogService(mockContext.Object); 
      var blogs = await service.GetAllBlogsAsync(); 

      Assert.AreEqual(3, blogs.Count); 
      Assert.AreEqual("AAA", blogs[0].Name); 
      Assert.AreEqual("BBB", blogs[1].Name); 
      Assert.AreEqual("ZZZ", blogs[2].Name); 
     } 
    } 
} 
+0

我会投票这个答案,如果它添加一些细节,而不是链接。 – jpierson

+0

根据您的要求更新了接受的答案。 – xDisruptor

您应该关注单元测试您的应用程序(逻辑),而不是实体框架 - 这是Microsoft的工作。为您的数据层添加一个漂亮的界面,以便在为您的(应用程序)业务逻辑编写单元测试时,可以将该界面嘲笑。

+0

感谢您的调整。我了解您的思路,我会自己走这条路,但我研究了复合视图中的人的建议,到目前为止,他们建议以原始帖子中显示的方式嘲笑dbset。即使微软似乎认识到,嘲笑dbset和ef是一般的东西是“随便”(https://msdn.microsoft.com/en-us/library/dn314429(v=vs.113).aspx)。您是否会友好地指点我一次资源展示 - 在存储库实现方面与“接口”数据层方面一致? (我不想重新发明*造成不好的错误) – xDisruptor

+0

我认为这取决于您的应用程序如何工作以及您愿意花些什么时间。对我来说,你发布的测试和在msdn页面上的测试只是证明你可以将内容存储在模拟器中,如果这是你想要的,那么我建议试着找一个嘲笑EF的指南。在我的应用程序中,我尝试隔离接口后面的数据访问(这对我的业务逻辑有意义),以便我可以更改数据源(数据库,磁盘上调用服务或不是什么的文件)。然后我编写单元测试来测试我的业务逻辑。但我没有看到数据访问作为核心业务... – Jocke