在 Generic 上模拟 DbSet<T>.Find() 是正确的方法吗?
Is mocking DbSet<T>.Find() on Generic the right way?
在 C# 和 EFCore 中,我使用以下方法模拟 DbSet 可查询对象:
public static DbSet<T> GetQueryableMockDbSet<T>(List<T> sourceList) where T : class
{
var queryable = sourceList.AsQueryable();
var dbSet = new Mock<DbSet<T>>();
dbSet.As<IQueryable<T>>().Setup(m => m.Provider).Returns(queryable.Provider);
dbSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(queryable.Expression);
dbSet.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(queryable.ElementType);
dbSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(() => queryable.GetEnumerator());
dbSet.Setup(d => d.Add(It.IsAny<T>())).Callback<T>((s) => sourceList.Add(s));
return dbSet.Object;
}
这适用于多种情况,但我不支持 EF .Find() 方法。
我想像这样添加它:
dbSet.Setup(d => d.Find(It.IsAny<int>())).Callback<int>((s) => sourceList.Where(x => x.ID == s));
...但是 T 是通用的,我不能依赖 ID 属性 来检查它。
我考虑过的两个解决方法:
#1:将签名的 where T : class 更改为某种 where T : IFindable 我始终将 .ID 用作 属性,这样就可以了,但随后需要我在我的不可测试的 classes 中添加一堆 IFinables,这很丑陋,或者
#2:输入回调,使用反射读取字段name/values,查找ID字段并匹配值
#2 可能是我要做的。它很丑陋,但至少它包含在这个测试助手中,并且不需要对非测试代码的回推。
有没有人有更巧妙的处理方法?
正如其他人在评论中提到的,使用 in-memory 提供商是推荐的方法。
https://docs.microsoft.com/en-us/ef/core/miscellaneous/testing/
We use test doubles for internal testing of EF Core. However, we never try to mock DbContext or IQueryable. Doing so is difficult, cumbersome, and fragile. Don't do it.
如果你想继续模拟,你可以试试这个:
dbSet.Setup(d => d.Find(It.IsAny<int>())).Returns<int>(id => (T)sourceList.OfType<IFindable>().FirstOrDefault(x => x.ID == id));
这样你只需要在你正在测试的 类 上实现 IFindable
。
此外,我会打电话给 IFindable
IEntity
在 C# 和 EFCore 中,我使用以下方法模拟 DbSet 可查询对象:
public static DbSet<T> GetQueryableMockDbSet<T>(List<T> sourceList) where T : class
{
var queryable = sourceList.AsQueryable();
var dbSet = new Mock<DbSet<T>>();
dbSet.As<IQueryable<T>>().Setup(m => m.Provider).Returns(queryable.Provider);
dbSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(queryable.Expression);
dbSet.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(queryable.ElementType);
dbSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(() => queryable.GetEnumerator());
dbSet.Setup(d => d.Add(It.IsAny<T>())).Callback<T>((s) => sourceList.Add(s));
return dbSet.Object;
}
这适用于多种情况,但我不支持 EF .Find() 方法。
我想像这样添加它:
dbSet.Setup(d => d.Find(It.IsAny<int>())).Callback<int>((s) => sourceList.Where(x => x.ID == s));
...但是 T 是通用的,我不能依赖 ID 属性 来检查它。
我考虑过的两个解决方法:
#1:将签名的 where T : class 更改为某种 where T : IFindable 我始终将 .ID 用作 属性,这样就可以了,但随后需要我在我的不可测试的 classes 中添加一堆 IFinables,这很丑陋,或者
#2:输入回调,使用反射读取字段name/values,查找ID字段并匹配值
#2 可能是我要做的。它很丑陋,但至少它包含在这个测试助手中,并且不需要对非测试代码的回推。
有没有人有更巧妙的处理方法?
正如其他人在评论中提到的,使用 in-memory 提供商是推荐的方法。
https://docs.microsoft.com/en-us/ef/core/miscellaneous/testing/
We use test doubles for internal testing of EF Core. However, we never try to mock DbContext or IQueryable. Doing so is difficult, cumbersome, and fragile. Don't do it.
如果你想继续模拟,你可以试试这个:
dbSet.Setup(d => d.Find(It.IsAny<int>())).Returns<int>(id => (T)sourceList.OfType<IFindable>().FirstOrDefault(x => x.ID == id));
这样你只需要在你正在测试的 类 上实现 IFindable
。
此外,我会打电话给 IFindable
IEntity