在 Entity Framework 6.1 中模拟 DbContext
Mocking DbContext in Entity Framework 6.1
我发现了许多示例(显然)显示了使用 EF 6 模拟 DbContext 的清晰工作示例,但是,none 其中的示例似乎对我有用,我不完全确定为什么。
这是我设置模拟的单元测试代码;
var mockData = new List<User> { new User { Email = "my@email.com", Id = 1 } }.AsQueryable();
var mockSet = new Mock<DbSet<User>>();
mockSet.As<IQueryable<User>>().Setup(m => m.Provider).Returns(mockData.Provider);
mockSet.As<IQueryable<User>>().Setup(m => m.Expression).Returns(mockData.Expression);
mockSet.As<IQueryable<User>>().Setup(m => m.ElementType).Returns(mockData.ElementType);
mockSet.As<IQueryable<User>>().Setup(m => m.GetEnumerator()).Returns(mockData.GetEnumerator());
var mockContext = new Mock<MyDbContext>();
mockContext.Setup(c => c.Users).Returns(mockSet.Object);
然后调用我正在测试的服务;
var service = new UsersService(mockContext.Object);
var user = service.GetById(1);
这将引发 NullReferenceException,因为基础 DbSet 始终为空。该代码执行以下操作;
在基类中;
public IEnumerable<T> GetAll()
{
return _dbSet.AsEnumerable();
}
在子类中;
public User GetById(int id)
{
return GetAll().FirstOrDefault(x => x.Id == id);
}
请注意,尽管 SO 上还有其他问题似乎相关,但它们不涉及 EF 6。
作为参考,这是一篇 MSDN 文章,对相同的代码进行了修改使其可以编译。
https://msdn.microsoft.com/en-us/data/dn314429.aspx
编辑:
降低了 UserService 的复杂性(它的用途 generics/interfaces),现在的代码很简单;
public User GetById(int id)
{
return _dbContext.Set<User>().FirstOrDefault(x => x.Id == id);
}
如果我将其进一步更改为;
var dbSet = _dbContext.Set<User>();
return dbSet.FirstOrDefault(x => x.Id == id);
我可以清楚地看到 dbSet 为空。
编辑 2
根据 wablab 的建议,模拟 .Set 似乎解决了问题。
DbSet 的通用方法也归功于 Vladyslav Kushnir。
任何可能需要它的人的工作代码;
private static Mock<DbSet<T>> GetDbSetMock<T>(IEnumerable<T> items = null) where T : class
{
if (items == null)
{
items = new T[0];
}
var dbSetMock = new Mock<DbSet<T>>();
var q = dbSetMock.As<IQueryable<T>>();
q.Setup(x => x.GetEnumerator()).Returns(items.GetEnumerator);
return dbSetMock;
}
var mockContext = new Mock<Model1>();
var users = new List<User> { new User { Email = "my@email.com", Id = 1 } };
mockContext.Setup(x => x.Set<User>()).Returns(GetDbSetMock(users).Object);
var service = new UsersService(mockContext.Object);
var user = service.GetById(1);
private Mock<DbSet<T>> GetDbSetMock<T>(IEnumerable<T> items = null) where T : class
{
if (items == null)
{
items = new T[0];
}
var dbSetMock = new Mock<DbSet<T>>();
var q = dbSetMock.As<IQueryable<T>>();
q.Setup(x => x.GetEnumerator()).Returns(items.GetEnumerator);
return dbSetMock;
}
这是我用于模拟 DbContext 的 DbSet 的非常有效的通用方法。这个方法的实际调用是:
var contextMock = new Mock<MyContext>();
contextMock.Setup(x => x.MyDbEntities).Returns(GetDbSetMock<MyDbEntity>().Object);
我认为您需要在 Set<User>()
方法上为 return 您的模拟创建一个设置。
我发现了许多示例(显然)显示了使用 EF 6 模拟 DbContext 的清晰工作示例,但是,none 其中的示例似乎对我有用,我不完全确定为什么。
这是我设置模拟的单元测试代码;
var mockData = new List<User> { new User { Email = "my@email.com", Id = 1 } }.AsQueryable();
var mockSet = new Mock<DbSet<User>>();
mockSet.As<IQueryable<User>>().Setup(m => m.Provider).Returns(mockData.Provider);
mockSet.As<IQueryable<User>>().Setup(m => m.Expression).Returns(mockData.Expression);
mockSet.As<IQueryable<User>>().Setup(m => m.ElementType).Returns(mockData.ElementType);
mockSet.As<IQueryable<User>>().Setup(m => m.GetEnumerator()).Returns(mockData.GetEnumerator());
var mockContext = new Mock<MyDbContext>();
mockContext.Setup(c => c.Users).Returns(mockSet.Object);
然后调用我正在测试的服务;
var service = new UsersService(mockContext.Object);
var user = service.GetById(1);
这将引发 NullReferenceException,因为基础 DbSet 始终为空。该代码执行以下操作;
在基类中;
public IEnumerable<T> GetAll()
{
return _dbSet.AsEnumerable();
}
在子类中;
public User GetById(int id)
{
return GetAll().FirstOrDefault(x => x.Id == id);
}
请注意,尽管 SO 上还有其他问题似乎相关,但它们不涉及 EF 6。
作为参考,这是一篇 MSDN 文章,对相同的代码进行了修改使其可以编译。
https://msdn.microsoft.com/en-us/data/dn314429.aspx
编辑:
降低了 UserService 的复杂性(它的用途 generics/interfaces),现在的代码很简单;
public User GetById(int id)
{
return _dbContext.Set<User>().FirstOrDefault(x => x.Id == id);
}
如果我将其进一步更改为;
var dbSet = _dbContext.Set<User>();
return dbSet.FirstOrDefault(x => x.Id == id);
我可以清楚地看到 dbSet 为空。
编辑 2
根据 wablab 的建议,模拟 .Set 似乎解决了问题。
DbSet 的通用方法也归功于 Vladyslav Kushnir。
任何可能需要它的人的工作代码;
private static Mock<DbSet<T>> GetDbSetMock<T>(IEnumerable<T> items = null) where T : class
{
if (items == null)
{
items = new T[0];
}
var dbSetMock = new Mock<DbSet<T>>();
var q = dbSetMock.As<IQueryable<T>>();
q.Setup(x => x.GetEnumerator()).Returns(items.GetEnumerator);
return dbSetMock;
}
var mockContext = new Mock<Model1>();
var users = new List<User> { new User { Email = "my@email.com", Id = 1 } };
mockContext.Setup(x => x.Set<User>()).Returns(GetDbSetMock(users).Object);
var service = new UsersService(mockContext.Object);
var user = service.GetById(1);
private Mock<DbSet<T>> GetDbSetMock<T>(IEnumerable<T> items = null) where T : class
{
if (items == null)
{
items = new T[0];
}
var dbSetMock = new Mock<DbSet<T>>();
var q = dbSetMock.As<IQueryable<T>>();
q.Setup(x => x.GetEnumerator()).Returns(items.GetEnumerator);
return dbSetMock;
}
这是我用于模拟 DbContext 的 DbSet 的非常有效的通用方法。这个方法的实际调用是:
var contextMock = new Mock<MyContext>();
contextMock.Setup(x => x.MyDbEntities).Returns(GetDbSetMock<MyDbEntity>().Object);
我认为您需要在 Set<User>()
方法上为 return 您的模拟创建一个设置。