77

I'm trying to set up a mock DbSet for testing purposes. I used the tutorial here, http://www.loganfranken.com/blog/517/mocking-dbset-queries-in-ef6/ and slightly modified it so calling GetEnumerator returns a new enumerator each time (another problem i was having). However, I am having difficulty adding items to the DbSet.

The output is preCount = 3 postCount = 3. However, I expect it to be precount = 3 postCount = 4. Any help is greatly appreciated.

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;
    }
user1080952
  • 1,073
  • 2
  • 10
  • 15
  • Very nice method to encapsulate creating the dbSet. Any chance you have updated this to support async queries? – Paul Gorbas Jun 09 '17 at 16:47
  • 1
    This, at least for the moment with .net core 1.0, will answer the async issue: [How to mock an async repository with Entity Framework Core](https://stackoverflow.com/questions/40476233/how-to-mock-an-async-repository-with-entity-framework-core) – Paul Gorbas Jun 09 '17 at 17:44

2 Answers2

160

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
Liam
  • 27,717
  • 28
  • 128
  • 190
Daniel Dušek
  • 13,683
  • 5
  • 36
  • 51
  • The problem is that when you Add something to the database you'll have to manually set the navigation properties, while this mocking will save the object as it is. I wonder is there is a way to emulate EF's behaviour about this. – tocqueville Jun 22 '16 at 19:24
  • 4
    The point of mocking EF is because it's not desired to bring a database into your tests. It's better to have fast-running, compartmentalized tests that test only a specific API. Another option is an https://msdn.microsoft.com/en-us/data/dn314431.aspx "in-memory double", however, in-memory doubles and mocking are essentially the same thing. – andrew Aug 31 '16 at 02:45
  • 1
    Any chance you have updated this to support async queries? – Paul Gorbas Jun 09 '17 at 16:45
  • This, at least for the moment with .net core 1.0, will answer the async issue: [How to mock an async repository with Entity Framework Core](https://stackoverflow.com/questions/40476233/how-to-mock-an-async-repository-with-entity-framework-core) – Paul Gorbas Jun 09 '17 at 17:46
  • 5
    Seriously if i didn't land on this post and the answer, i would have lost forever >.< Thank you so much, both the question and the answer! – zeroflaw Feb 02 '18 at 06:36
  • I don't think this mock works if you try and call .Include() on it. – Simon Green Jan 07 '20 at 18:02
  • Excellent... now, if someone can help me implement `Find()` on a generic `T`, that'd be even more excellent! (I've had this Q/A in my "library" for years now, but just needed to mock Find) – jleach Aug 13 '20 at 11:10
3

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.

jleach
  • 7,410
  • 3
  • 33
  • 60