Moq 模拟 EF DbContext

Moq mocking EF DbContext

我有一个与 Entity Framework 交互的存储库模式。 我想 运行 对存储库进行一些单元测试,因此,我想模拟 DbContext。

所以我创建了一个单元测试项目 (.Net Core 3.1),使用 Moq 作为单元测试包,一切似乎都正常,但是当我在我的存储库上执行 .ToListAsync() 时,它会抛出以下异常:

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

源代码:

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

如果我将填充的 ID 发送到存储库,它工作正常,所以这似乎只对 .ToListAsync() 不可行。

我有点卡在这里,我该怎么做才能克服这个问题?

您无法模拟 DbSet 查询功能。这是解释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.

为了执行异步读取操作 (ToListAsync()),您需要模拟一个名为“IDBAsyncQueryProvider”的附加接口。

Here's 是必需的 link 您可以关注。它位于“使用异步查询进行测试”标题下