如何将项目添加到 Mock DbSet(使用 Moq)
How to add an item to a Mock DbSet (using Moq)
我正在尝试设置模拟 DbSet 以进行测试。我在这里使用了教程 http://www.loganfranken.com/blog/517/mocking-dbset-queries-in-ef6/ 并略微修改了它,因此每次调用 GetEnumerator returns 一个新的枚举器(我遇到的另一个问题)。但是,我很难将项目添加到 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;
}
myDbSet
不是 DbSet
的真正实现,而是一个模拟,这意味着它是 fake 并且需要为您需要的所有方法进行设置。 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
时对约定做一些假设。
(希望这不会越界,我已经将此 Q/A 加入书签多年,并且一直在寻找 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
(这是我的标准约定),但这也可以扩展以获得更强大的支持。
我正在尝试设置模拟 DbSet 以进行测试。我在这里使用了教程 http://www.loganfranken.com/blog/517/mocking-dbset-queries-in-ef6/ 并略微修改了它,因此每次调用 GetEnumerator returns 一个新的枚举器(我遇到的另一个问题)。但是,我很难将项目添加到 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;
}
myDbSet
不是 DbSet
的真正实现,而是一个模拟,这意味着它是 fake 并且需要为您需要的所有方法进行设置。 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
时对约定做一些假设。
(希望这不会越界,我已经将此 Q/A 加入书签多年,并且一直在寻找 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
(这是我的标准约定),但这也可以扩展以获得更强大的支持。