Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
445 views
in Technique[技术] by (71.8m points)

asp.net - Unit-testing .ToListAsync() using an in-memory

Below is the kind of test that is failing upon .ShouldNotThrow() due to .ToListAsync() not being supported by in-memory dbsets (I don't have the exact wording handy but you get the picture). In case it's of any importance, I'm trying to mockup the dbset provided by Entity Framework ver. 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();
    }
}

The above test works as intended if .ToList() is used synchronously in place of the async-based .ToListAsync(). Also the repository works fine when used from within the actual asp.net.

So what's the correct way to go about mocking up the dbset for .ToListAsync() to work in these unit-tests?

P.S.: The project I've been unit-testing can be found here:

       https://bitbucket.org/dsidirop/gighub

The unit-tests that fail due to .ToListAsync() are marked with a comment 'fails for the time being'.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Hats off to Bradford Dillon for providing the correct answer:

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

The proper way to create unit tests for async methods of repositories is to first create these utility-mockup classes:

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

And then use them like so:

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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...