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](IQueryable
1
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 您可以关注。它位于“使用异步查询进行测试”标题下
我有一个与 Entity Framework 交互的存储库模式。 我想 运行 对存储库进行一些单元测试,因此,我想模拟 DbContext。
所以我创建了一个单元测试项目 (.Net Core 3.1),使用 Moq 作为单元测试包,一切似乎都正常,但是当我在我的存储库上执行 .ToListAsync() 时,它会抛出以下异常:
System.NotImplementedException : The method or operation is not implemented. Stack Trace: IAsyncEnumerable.GetAsyncEnumerator(CancellationToken cancellationToken) ConfiguredCancelableAsyncEnumerable
1.GetAsyncEnumerator() EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable
1 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 overIQueryable
. As a result, when some people talk about "mockingDbSet
", what they really mean is that they create aDbSet
backed by an in-memory collection, and then evaluate query operators against that collection in memory, just like a simpleIEnumerable
. 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 您可以关注。它位于“使用异步查询进行测试”标题下