3

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'.

XDS
  • 3,786
  • 2
  • 36
  • 56
  • 3
    There are a lot of hoops to jump through to mock an EF DbContext fully. Link only answers are frowned upon, but this is a lot of information to type on a phone so I will just leave it as a comment. [Mocking an EF DbContext](https://msdn.microsoft.com/en-us/library/dn314429(v=vs.113).aspx). – Bradford Dillon Dec 25 '16 at 22:00

2 Answers2

8

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); 
        } 
    } 
}
XDS
  • 3,786
  • 2
  • 36
  • 56
  • I would vote this answer up if it added some details other than a link. – jpierson Sep 22 '17 at 15:40
  • Thank you! I ran into an issue this morning where adding a `.Where()` to my call caused exceptions to be thrown because it was no longer an asynchronous queryable. The issue was I didn't configure my Provider as `new TestDbAsyncQueryProvider(data.Provider)`. I only did `data.Provider`. You saved me a LOT of strugge. – krillgar Dec 23 '20 at 14:29
  • I went through all of this multiple times and still get the exact same exception on .NET 5.0 – code_disciple Jul 14 '21 at 18:43
  • 1
    For anyone coming across this in relation to EF Core (5/6) especially around how the ExecuteAsync method returns a `TResult` instead of `Task`. I had run into this brick wall and there is a good explanation on the EF Core GitHub (https://github.com/dotnet/efcore/issues/16864) To mock out `IQueryable` a renewed search turned up a NuGet package called MockQueryable which provides an extension method for setting something fully suitable up. (https://github.com/romantitov/MockQueryable) – Steve Py Aug 18 '22 at 05:06
1

You should focus on unit testing your application (logic), not Entity Framework - that is the work for Microsoft. Add a nice interface for your data layer, so that you can mock that interface away when writing unit tests for your (application) business logic.

Jocke
  • 2,189
  • 1
  • 16
  • 24
  • Thanks for tuning in. I understand your line of thinking and I would have gone that way myself but I studied the suggestions of people in pluralsight and so far they recommend mocking dbsets the way shown in the original post. Even microsoft seems to recognize that mocking dbset and ef is general is something "casual" (https://msdn.microsoft.com/en-us/library/dn314429(v=vs.113).aspx). Would you be kind enough to point me to a resource show-casing your approach in terms of repository implementation vs an "interfaced" data-layer? (I don't want to re-invent the wheel making noobish blunders) – XDS Dec 26 '16 at 23:28
  • 2
    I think it depends on how your application works and what you are willing to spend your time on. To me the test you have posted and the ones on the msdn page just proved that you can store thing in memory with mocks, if that is what you want then I suggest try to find a guide for mocking EF. In my applications I try to isolate data access behind a interface (that makes sense for my business logic) so that I can change data soure (database, files on disk calling a service or what not). Then I write unit test to test my business logic. But I do not see data access as core business... – Jocke Dec 27 '16 at 12:09