如何向 Mock DbSet 添加项(使用 Moq)

我试图为测试目的设置一个模拟 DbSet。我在这里使用了教程 http://www.loganfranken.com/blog/517/mocking-dbset-queries-in-ef6/,并对其稍作修改,以便调用 GetEnumerator 每次返回一个新的枚举器(这是我遇到的另一个问题)。但是,我在向 DbSet 添加项目时遇到了困难。

输出是 preCount = 3 postCount = 3。但是,我希望它是 precount = 3 postCount = 4。非常感谢您的帮助。

static void Main(string[] args)
{
Debug.WriteLine("hello debug");


List<string> stringList = new List<string>
{
"a", "b", "c"
};


DbSet<string> myDbSet = GetQueryableMockDbSet(stringList);
int preCount = myDbSet.Count();
myDbSet.Add("d");
int postCount = myDbSet.Count();
Debug.WriteLine("preCount = " + preCount + " postCount = " + postCount);
}


private 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());


return dbSet.Object;
}
48011 次浏览

myDbSet不是 DbSet的真正实现,而是一个 mock,这意味着它是 假的,需要为所有需要的方法进行设置。Add也不例外,所以它需要被设置为做你需要的,否则它什么也不做。

添加如下内容,当调用 myDbSet.Add("d");时,将“ d”添加到列表中,以后可以返回。

dbSet.Setup(d => d.Add(It.IsAny<T>())).Callback<T>((s) => sourceList.Add(s));

完整的密码

private 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;
}

输出

hello debug
preCount = 3 postCount = 4

为了处理 .Find(),我们可以以类似的方式使用反射,在使用 Find时对约定做一些假设。

(希望这没有过分,我已经把这个问答标记了很多年,正在寻找一个 Find实现... ...)

按照以下方式实施另一个帮手:

static object find(IEnumerable<object> oEnumerable, object[] keys)
{
// assumptions: primary key of object is named ID
// primary key of object is an int
// keys passed to .Find() method is a single value of int type
foreach (var o in oEnumerable)
{
var t = o.GetType();
var prop = t.GetProperty("ID");
if (prop != null)
{
if (prop.PropertyType == typeof(int))
{
if ((int)prop.GetValue(o) == (int)keys[0])
{
return o;
}
}
}
}
return null;
}

在 Daniel 上面提供的模拟设置例子中:

dbSet.Setup(d => d.Find(It.IsAny<object[]>())).Returns((object[] oArray) => find(sourceList, oArray) as T);

因为我们没有办法(或者希望,通常)像 EF 那样确定主键,所以我假设“ ID”是键字段名(与我自己的约定相符) ,但是这可以扩展到接受许多变体。我还假设只有一个整数被传递给 Find(这是我的标准惯例) ,但是这也可以扩展以获得更健壮的支持。