Home > Enterprise >  Moq mocking EF DbContext
Moq mocking EF DbContext

Time:02-25

I have a Repository pattern that interacts with Entity Framework. I'd like to run some unit tests on the repository, and for this reason, I would like to mock DbContext.

So I've created a unit test project (.Net Core 3.1), using Moq as package for unit testing, everything seems to be ok, but when I perform a .ToListAsync() on my repository it throws the following exception:

System.NotImplementedException : The method or operation is not implemented. Stack Trace:  IAsyncEnumerable.GetAsyncEnumerator(CancellationToken cancellationToken) ConfiguredCancelableAsyncEnumerable1.GetAsyncEnumerator() EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable1 source, CancellationToken cancellationToken)

The source code:

public class Customer
{
    public Guid Id { get; set; }

    public string Name { get; set; }
}

public class CustomersDbContext : DbContext
{
    public virtual DbSet<Customer> Customers { get; set; }

    public CustomersDbContext(DbContextOptions<Customer> options) : base(options) { }
}

public interface ICustomerRepository
{
    Task<IEnumerable<Customer>> GetCustomersAsync(Guid? customerId);
}
public class CustomerRepository : ICustomerRepository
{

    private readonly CustomersDbContext _dbContext;

    public CustomerRepository(CustomersDbContext dbContext)
    {
        _dbContext = dbContext;

        _dbContext.Database.EnsureCreated();
    }

    public async Task<IEnumerable<Customer>> GetCustomersAsync(Guid? customerId)
    {
        IEnumerable<Customer> customers = null;

        if (customerId.HasValue)
        {
            var customer = await _dbContext.Customers.FindAsync(new object[] { customerId.Value }, CancellationToken.None);

            if (customer != null)
                customers = new List<Customer>() { customer };
        }
        else
        {
            customers = await _dbContext.Customers.ToListAsync(CancellationToken.None);
        }

        return customers;
    }
}


public class CustomerServiceUnitTests
{
    private Mock<CustomersDbContext> GetCustomerDbContextMock()
    {
        var data = new List<Customer>()
        {
            new Customer()
            {
                Id = Guid.NewGuid(),
                Name = "Name 1"
            },
            new Customer()
            {
                Id = Guid.NewGuid(),
                Name = "Name 2"
            }
        }.AsQueryable();

        var mockSet = new Mock<DbSet<Customer>>();
        mockSet.As<IQueryable<Customer>>().Setup(m => m.Provider).Returns(data.Provider);
        mockSet.As<IQueryable<Customer>>().Setup(m => m.Expression).Returns(data.Expression);
        mockSet.As<IQueryable<Customer>>().Setup(m => m.ElementType).Returns(data.ElementType);
        mockSet.As<IQueryable<Customer>>().Setup(m => m.GetEnumerator()).Returns(data.GetEnumerator());

        var optionsBuilder = new DbContextOptions<CustomersDbContext>();

        var mockContext = new Mock<CustomersDbContext>(optionsBuilder);

        Mock<DatabaseFacade> databaseFacade = new Mock<DatabaseFacade>(mockContext.Object);
        databaseFacade.Setup(d => d.EnsureCreatedAsync(CancellationToken.None)).Returns(Task.FromResult(true));

        mockContext.Setup(c => c.Database).Returns(databaseFacade.Object);
        mockContext.Setup(c => c.Customers).Returns(mockSet.Object);

        return mockContext;
    }

    [Fact]
    public async Task Infrastructure_CustomerRepository_GetAll()
    {

        var mockContext = this.GetCustomerDbContextMock();

        ICustomerRepository customerRepository = new CustomerRepository(mockContext.Object);

        var customers = await customerRepository.GetCustomersAsync(null);

        Assert.NotNull(customers);
        Assert.Equal(2, customers.Count());
    }
}

If I send an ID filled to the repository it works fine, so this seems to be not ok only for .ToListAsync().

I'm kinda stuck here, what can I do to overcome this?

CodePudding user response:

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.

CodePudding user response:

In order to execute Asynchronous read operation (ToListAsync()) you need to mock an additional interface called "IDBAsyncQueryProvider".

Here's is the required link you can follow. It is under the heading "Testing with async queries"

  • Related