【问题标题】:Unit-testing .ToListAsync() using an in-memory使用内存对 .ToListAsync() 进行单元测试
【发布时间】:2016-12-25 21:53:44
【问题描述】:

以下是由于 .ToListAsync() 不受内存数据库集支持而在 .ShouldNotThrow() 上失败的那种测试(我没有方便的确切措辞,但你明白了)。万一它很重要,我正在尝试模拟实体框架版本提供的数据库集。 6.1.3:

[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 中使用时,存储库也可以正常工作。

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

P.S.:我一直在进行单元测试的项目可以在这里找到:

       https://bitbucket.org/dsidirop/gighub

由于 .ToListAsync() 而失败的单元测试标有注释“暂时失败”。

【问题讨论】:

  • 要完全模拟 EF DbContext 有很多麻烦。仅链接的答案不受欢迎,但这是在手机上输入的大量信息,所以我将其作为评论留下。 Mocking an EF DbContext.

标签: asp.net entity-framework unit-testing


【解决方案1】:

感谢 Bradford Dillon 提供了正确的答案:

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); 
        } 
    } 
}

【讨论】:

  • 如果这个答案添加了链接以外的一些细节,我会投票赞成。
  • 谢谢!今天早上我遇到了一个问题,在我的调用中添加 .Where() 会导致抛出异常,因为它不再是异步可查询的。问题是我没有将我的 Provider 配置为 new TestDbAsyncQueryProvider&lt;T&gt;(data.Provider)。我只做了data.Provider。你为我省了很多力气。
  • 我多次经历了这一切,但在 .NET 5.0 上仍然得到完全相同的异常
【解决方案2】:

您应该专注于对您的应用程序(逻辑)进行单元测试,而不是实体框架 - 这是 Microsoft 的工作。为您的数据层添加一个漂亮的接口,以便在为您的(应用程序)业务逻辑编写单元测试时模拟该接口。

【讨论】:

  • 感谢您收看。我理解您的思路,我自己也会这样做,但我研究了人们的建议,到目前为止,他们建议按照原始帖子中显示的方式模拟 dbset .甚至 microsoft 似乎也认识到嘲笑 dbset 和 ef 是一般的“随意”(msdn.microsoft.com/en-us/library/dn314429(v=vs.113).aspx)。您能否向我指出一个资源,展示您在存储库实现与“接口”数据层方面的方法? (我不想重新发明轮子,制造愚蠢的错误)
  • 我认为这取决于您的应用程序如何工作以及您愿意花时间在什么上。对我来说,您发布的测试和 msdn 页面上的测试证明您可以使用模拟将内容存储在内存中,如果这是您想要的,那么我建议您尝试找到模拟 EF 的指南。在我的应用程序中,我尝试隔离接口后面的数据访问(这对我的业务逻辑有意义),以便我可以更改数据源(数据库、磁盘上的文件调用服务或其他什么)。然后我编写单元测试来测试我的业务逻辑。但我不认为数据访问是核心业务......
猜你喜欢
  • 2018-06-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-08
  • 1970-01-01
  • 1970-01-01
  • 2020-04-13
相关资源
最近更新 更多