2016-10-04 63 views
3

我目前使用的扩展方法一般模拟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是否提供了查找主键的任何方式,还是只在连接到数据库后才动态地知道?

+2

你在找这样的东西:[link](http://stackoverflow.com/a/25199983/5048049)否则你可以更好地解释你在找什么? – peval27

+1

这是我描述的第一个选择。这只是针对ActiveLoan集合做出特定的模拟,而不是针对任何通用集合的通用模拟。 –

回答

4

经过一段时间的思考,我想我已经找到了目前可用的“最佳”解决方案。我只是有一系列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); 
     } 
     ...  
+0

不错。由于我所有的实体都具有相同的主键“Id”,因此我可以通过将“y.MySetKey”替换为“y.GetType()。GetProperty(”Id“).GetValue(y)”来使这更通用。 – Jared

1

据我所知,这个问题没有“最佳实践”的答案,但这是我如何接近它。我为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的假设被用来作为主键这似乎是你如何工作的,但如果你需要更多的灵活性原则应该是很容易适应复合键等

0

我在下面的类结束:

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); 
} 
相关问题