How to Mock an Async Repository with Entity Framework Core

How to mock an async repository with Entity Framework Core

Thanks to @Nkosi for pointing me to a link with an example of doing the same thing in EF 6: https://msdn.microsoft.com/en-us/library/dn314429.aspx. This didn't work exactly as-is with EF Core, but I was able to start with it and make modifications to get it working. Below are the test classes that I created to "mock" IAsyncQueryProvider:

internal class TestAsyncQueryProvider<TEntity> : IAsyncQueryProvider
{
private readonly IQueryProvider _inner;

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

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

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

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

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

public IAsyncEnumerable<TResult> ExecuteAsync<TResult>(Expression expression)
{
return new TestAsyncEnumerable<TResult>(expression);
}

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

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

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

public IAsyncEnumerator<T> GetEnumerator()
{
return new TestAsyncEnumerator<T>(this.AsEnumerable().GetEnumerator());
}

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

internal class TestAsyncEnumerator<T> : IAsyncEnumerator<T>
{
private readonly IEnumerator<T> _inner;

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

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

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

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

And here is my updated test case that uses these classes:

[Fact]
public async Task GetCompanyProductURLAsync_ReturnsNullForInvalidCompanyProduct()
{
var companyProducts = Enumerable.Empty<CompanyProductUrl>().AsQueryable();

var mockSet = new Mock<DbSet<CompanyProductUrl>>();

mockSet.As<IAsyncEnumerable<CompanyProductUrl>>()
.Setup(m => m.GetEnumerator())
.Returns(new TestAsyncEnumerator<CompanyProductUrl>(companyProducts.GetEnumerator()));

mockSet.As<IQueryable<CompanyProductUrl>>()
.Setup(m => m.Provider)
.Returns(new TestAsyncQueryProvider<CompanyProductUrl>(companyProducts.Provider));

mockSet.As<IQueryable<CompanyProductUrl>>().Setup(m => m.Expression).Returns(companyProducts.Expression);
mockSet.As<IQueryable<CompanyProductUrl>>().Setup(m => m.ElementType).Returns(companyProducts.ElementType);
mockSet.As<IQueryable<CompanyProductUrl>>().Setup(m => m.GetEnumerator()).Returns(() => companyProducts.GetEnumerator());

var contextOptions = new DbContextOptions<SaasDispatcherDbContext>();
var mockContext = new Mock<SaasDispatcherDbContext>(contextOptions);
mockContext.Setup(c => c.Set<CompanyProductUrl>()).Returns(mockSet.Object);

var entityRepository = new EntityRepository<CompanyProductUrl>(mockContext.Object);

var service = new CompanyProductService(entityRepository);

var result = await service.GetCompanyProductURLAsync(Guid.NewGuid(), "wot", Guid.NewGuid());

Assert.Null(result);
}

How to mock Entity Framework 6 Async methods?

You are right the problem is in your InnerDbContext.Set<T>(); statement.

In the current version of the EF (6.0.2) the DbContext.Set<T> method is not virtual so it cannot be mocked with Moq.

So you cannot easily make your test pass except by changing your design of the BaseRepository to not depend on the whole DbContext but on one DbSet<T>:

So something like:

public BaseRepository(DbSet<T> dbSet)
{
InnerDbSet = dbSet;
}

Then you can pass directly in your mocked DbSet.

Or you can create a wrapper interface for DbContext:

public interface IDbContext
{
DbSet<T> Set<T>() where T : class;
}

public class TimeSketchContext : DbContext, IDbContext
{
public virtual DbSet<EmployeeSkill> EmployeeSkill { get; set; }
}

Then use IDbContext in your BaseRepository:

public class BaseRepository<T> : IRepositoryBase<T> where T : class, IEntity, new()
{
protected readonly IDbContext InnerDbContext;
protected DbSet<T> InnerDbSet;

public BaseRepository(IDbContext innerDbContext)
{
InnerDbContext = innerDbContext;
InnerDbSet = InnerDbContext.Set<T>();
}

public virtual Task<T> FindAsync(long id)
{
return InnerDbSet.FirstOrDefaultAsync(x => x.Id == id);
}
}

And finally you just need to change two lines in your test to make it pass:

var mockContext = new Mock<IDbContext>();
mockContext.Setup(c => c.Set<EmployeeSkill>()).Returns(mockSet.Object);

Moq mocking EF DbContext

You cannot mock DbSet query functionality. This is explained in the docs:

Properly mocking DbSet query functionality is not possible, since queries are expressed via LINQ operators, which are static
extension method calls over IQueryable. As a result, when some
people talk about "mocking DbSet", what they really mean is that they
create a DbSet backed by an in-memory collection, and then evaluate
query operators against that collection in memory, just like a simple
IEnumerable. Rather than a mock, this is actually a sort of fake,
where the in-memory collection replaces the the real database.

Moq - Mock DbSet T .AddAsync throws no invocations performed

So it looks like EF Core is not so easily tested with async tasks such as SaveChangesAsync and AddAsync. In the end, I followed the MS guide for testing EF core and created a mock context. The only downside being I can only test happy path. Although error paths are tested by the service which consumes the repository, I was hoping for more test coverage on the repository layer.

Anyway, here's the spec

using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using MusicPortal.Core.Context;
using MusicPortal.Core.Repository;
using MusicPortal.Tests.Repository.ArtistRepository.TestHelpers;
using MusicPortal.Tests.Repository.ArtistRepository.TestHelpers.MockDB;
using NUnit.Framework;
using MockArtistRepository = MusicPortal.Repository.Repository.ArtistRepository;

namespace MusicPortal.Tests.Repository.ArtistRepository.AddNewArtist
{
[TestFixture]
public class GivenANewArtistToInsertIntoTheDb
{
private DbContextOptions<MusicPortalDbContext> _options;
private MusicPortalDatabaseResponse<bool> _mockResponse;

[OneTimeSetUp]
public async Task Setup()
{
_options = new MockDbFactory("MusicPortalDB").Options;

using (var context = new MusicPortalDbContext(_options))
{
var artistRepository = new MockArtistRepository(context);
_mockResponse = await artistRepository.AddNewArtist(MockRepositoryData.Artist);
}
}

[Test]
public void AndThenAPositiveResultIsReturned()
{
Assert.Null(_mockResponse.Exception);
Assert.IsTrue(_mockResponse.Response);
Assert.IsFalse(_mockResponse.HasError);
}

[Test]
public void ThenTheArtistShouldBeSavedWithNoProblem()
{
using (var context = new MusicPortalDbContext(_options))
{
Assert.AreEqual(1, context.Artists.Count());
}
}
}
}

and the Mock Database:

using System;
using Microsoft.EntityFrameworkCore;
using MusicPortal.Core.Context;
using MusicPortal.Core.DBModels;

namespace MusicPortal.Tests.Repository.ArtistRepository.TestHelpers.MockDB
{
public sealed class MockDbFactory
{
public DbContextOptions<MusicPortalDbContext> Options { get; }

public MockDbFactory(string dbName)
{
Options = new DbContextOptionsBuilder<MusicPortalDbContext>()
.UseInMemoryDatabase(dbName)
.Options;
}

public void AddArtistsToContext()
{
using (var context = new MusicPortalDbContext(Options))
{
context.Artists.Add(new Artist
{
City = "Orange County",
Country = "USA",
Events = null,
Genre = "Pop Punk",
Id = Guid.Parse("8a07504b-8152-4d8b-8e21-74bf64322ebc"),
Merchandise = null,
Name = "A Day To Remember",
ArtistType = "Band",
ProfileImageUrl = "https://placehold.it/30x30"
});

context.SaveChanges();
}
}
}
}

I hope this helps anyone looking at the same issue. The lesson learned is don't use Async unless you absolutely have to.

How to Moq setup for await with ToListAsync variable which is already queried as IQurable?

By install MockQueryable.Moq 5.0 package

using MockQueryable.Moq;

var requestMaster = RequestMaster();
var mock = requestMaster.BuildMock();
_mockGenericRepository.Setup(s => s.GetIQueryable<RequestMaster>()).Returns(mock);

working fine.

Asp.Net Mocking a Repository that returns a DbSet with .GetAll().ToListAsync()

I managed to solve my own problem.

The issue was that I was casting the result of _controller.Index() as Task whereas I should have been casting it as ViewResult instead. When the Task is complete ("await"), I receive a ViewResult that gets assigned to actionResult. By casting it as Task I was getting null.

Here's what it should have read:

 [Fact]
public async void Index_GetModelContainingAllUsers()
{
_userRepository.Setup(g => g.GetAll()).Returns(_context._dbsetUsers.Object);
var actionResult = await _controller.Index() as ViewResult;
var usersResult = actionResult?.Model as IEnumerable<User>;
Assert.Equal(4, usersResult.Count());
}

How to unit test my EF repository method using Moq?

You need to remove the using statements in the GetAll and Create methods:

public Record Create(Record record)
{
try
{
using (_dbContext)
{
var response = _dbContext.Records.Add(record); //erroring line
_dbContext.SaveChanges();
return response.Entity;
}
}
catch (Exception ex)
{
return null;
}
}

To:

public Record Create(Record record)
{
try
{
var response = _dbContext.Records.Add(record); //erroring line
_dbContext.SaveChanges();
return response.Entity;
}
catch (Exception ex)
{
return null;
}
}

You don't need to worry about disposing the service as the conatiner will do that for you in production. In your tests you can do this if you want to clean things up:

[TestMethod]
public async Task Create_Successfully()
{
using (var context = await GetDbContext())
{
var repository = new Repository(context);
var existingRecords = repository.GetAll();

repository.Create(new Record());
var newRecords = repository.GetAll();

Assert.AreEqual(3, existingRecords.Count());
Assert.AreEqual(4, newRecords.Count());
}
}


Related Topics



Leave a reply



Submit