有没有办法一般地用Moq模拟DbSet.Find方法?
我目前使用的扩展方法一般模拟DbSets作为一个列表:有没有办法一般地用Moq模拟DbSet.Find方法?
public static DbSet<T> AsDbSet<T>(this List<T> sourceList) where T : class
{
var queryable = sourceList.AsQueryable();
var mockDbSet = new Mock<DbSet<T>>();
mockDbSet.As<IQueryable<T>>().Setup(m => m.Provider).Returns(queryable.Provider);
mockDbSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(queryable.Expression);
mockDbSet.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(queryable.ElementType);
mockDbSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(queryable.GetEnumerator());
mockDbSet.Setup(x => x.Add(It.IsAny<T>())).Callback<T>(sourceList.Add);
mockDbSet.Setup(x => x.Remove(It.IsAny<T>())).Returns<T>(x => { if (sourceList.Remove(x)) return x; else return null; });
return mockDbSet.Object;
}
但是,我不能想出一个办法来嘲笑查找方法,它需要查询的表的主键。我可以在每个表的特定级别执行此操作,因为我可以检查数据库,获取PK,然后模拟该字段的Find方法。但是,我不能使用通用方法。
我想我也可以添加到EF自动生成的部分类中,以标记哪个字段是具有属性或某物的PK。但是我们有超过100个表格,如果您依靠人员来手动维护,那么代码会更难管理。
EF6是否提供了查找主键的任何方式,还是只在连接到数据库后才动态地知道?
经过一段时间的思考,我想我已经找到了目前可用的“最佳”解决方案。我只是有一系列if语句直接检查扩展方法中的类型。然后我转换为我需要的类型来设置查找行为,并在完成后将其转换回通用类型。这只是伪通用的,但我想不出更好的。
if (typeof(T) == typeof(MyFirstSet))
{
mockDbSet.Setup(x => x.Find(It.IsAny<object[]>())).Returns<object[]>(x => (sourceList as List<MyFirstSet>).FirstOrDefault(y => y.MyFirstSetKey == (Guid)x[0]) as T);
}
else if (typeof(T) == typeof(MySecondSet))
{
mockDbSet.Setup(x => x.Find(It.IsAny<object[]>())).Returns<object[]>(x => (sourceList as List<MySecondSet>).FirstOrDefault(y => y.MySecondSetKey == (Guid)x[0]) as T);
}
...
不错。由于我所有的实体都具有相同的主键“Id”,因此我可以通过将“y.MySetKey”替换为“y.GetType()。GetProperty(”Id“).GetValue(y)”来使这更通用。 – Jared
据我所知,这个问题没有“最佳实践”的答案,但这是我如何接近它。我为AsDbSet
方法添加了一个可选参数,用于标识主键,然后可以轻松地对Find
方法进行模拟。
public static DbSet<T> AsDbSet<T>(this List<T> sourceList, Func<T, object> primaryKey = null) where T : class
{
//all your other stuff still goes here
if (primaryKey != null)
{
mockSet.Setup(set => set.Find(It.IsAny<object[]>())).Returns((object[] input) => sourceList.SingleOrDefault(x => (Guid)primaryKey(x) == (Guid)input.First()));
}
...
}
我写这在一个单一的GUID的假设被用来作为主键这似乎是你如何工作的,但如果你需要更多的灵活性原则应该是很容易适应复合键等
我在下面的类结束:
public static class DbSetMocking
{
#region methods
public static IReturnsResult<TContext> ReturnsDbSet<TEntity, TContext>(this IReturns<TContext, DbSet<TEntity>> setup, ICollection<TEntity> entities, Func<object[], TEntity> find = null)
where TEntity : class where TContext : DbContext
{
return setup.Returns(CreateMockSet(entities, find).Object);
}
private static Mock<DbSet<T>> CreateMockSet<T>(ICollection<T> data, Func<object[], T> find)
where T : class
{
var queryableData = data.AsQueryable();
var mockSet = new Mock<DbSet<T>>();
mockSet.As<IQueryable<T>>().Setup(m => m.Provider).Returns(queryableData.Provider);
mockSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(queryableData.Expression);
mockSet.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(queryableData.ElementType);
mockSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(queryableData.GetEnumerator());
mockSet.SetupData(data, find);
return mockSet;
}
#endregion
}
哪个可以用于:
private static MyRepository SetupRepository(ICollection<Type1> type1s, ICollection<Type2> type2s)
{
var mockContext = new Mock<MyDbContext>();
mockContext.Setup(x => x.Type1s).ReturnsDbSet(type1s, o => type1s.SingleOrDefault(s => s.Secret == (Guid) o[ 0 ]));
mockContext.Setup(x => x.Type2s).ReturnsDbSet(type2s, o => type2s.SingleOrDefault(s => s.Id == (int) o[ 0 ]));
return new MyRepository(mockContext.Object);
}
你在找这样的东西:[link](http://*.com/a/25199983/5048049)否则你可以更好地解释你在找什么? – peval27
这是我描述的第一个选择。这只是针对ActiveLoan集合做出特定的模拟,而不是针对任何通用集合的通用模拟。 –