How to add an item to a Mock DbSet (using Moq)
myDbSet
is not real implementation of DbSet
but a mock which means it's fake and it needs to be setup for all methods you need. The Add
is not exception so it needs to be set up to do what you need otherwise it does nothing.
Add something like the following and when the myDbSet.Add("d");
is called then the 'd' is added to the list and can be returned later.
dbSet.Setup(d => d.Add(It.IsAny<T>())).Callback<T>((s) => sourceList.Add(s));
Complete code
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;
}
Output
hello debug
preCount = 3 postCount = 4
To handle .Find()
, we can use reflection in a similar manner, with a few assumptions about conventions when utilizing Find
.
(hopefully this isn't out of line, I've had this Q/A bookmarked for years, and was looking for a Find
implementation...)
Implement another helper as so:
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;
}
and in the mock setup example provided by Daniel above:
dbSet.Setup(d => d.Find(It.IsAny<object[]>())).Returns((object[] oArray) => find(sourceList, oArray) as T);
Beacuse we have no way (or desire, usually) to determine the primary key in the way that EF would, I'm making the assumption that "ID" is the key field name (which matches my own conventions), but this could be expanded upon to accept a number of variations. I'm also assuming that only one integer is passed to Find
(as is my standard convention), but this could also be expanded on for more robust support.