How to mock an Entity Framework DbContext and its DbSet properties

Entity Framework (EF) is a data access layer (DAL) that allows for easily accessing a database for your create, read, update, and delete (CRUD) actions. If you use Entity Framework, you don’t need to test your DAL CRUD actions. You just need to test your code that uses it.

With EF, you can mock your database, though it isn’t exactly easy. The database is interfaced through the DbContext. Tables are interfaces through a property of type DbSet where T is an object representing data in the table. For example, DbSet would represent your users. We can use a List to mock the database table. It would also be nice if we could easily have every table mocked by default.

Ok, now that we have established that EF 6 DbContext mocking isn’t easy, let’s change that. Let’s make it easy.

Using the following two classes, you can easily mock your DbContext with a single line of code:

MockDbContext = EntityFrameworkMockHelper.GetMockContext<MyDbContext>();

Note: While this single line of code successfully mocks your DbContext, it doesn’t add any default data. You still have to do that work yourself, but now it should be easy because you can use the very easy to use Add method on any table.

MockDbContext.Object.People.Add(new Person{ FirstName = "John", LastName = "Doe" });

Or if you have a List of sample data already created, you can add that list with AddRange.

MockDbContext.Object.People.AddRange(SamplePeople);

And here are the two classes I wrote to help with this. I must admit, I spent over three days researching this and getting this working. So hopefully, this saves you from having to do the same.

using Moq;
using System.Collections.Generic;
using System.Data.Entity;

namespace LANDesk.Licensing.WebServices.Tests.Data
{
    public class MockedDbContext<T> : Mock<T> where T : DbContext
    {
        public Dictionary<string, object> Tables
        {
            get { return _Tables ?? (_Tables = new Dictionary<string, object>()); }
        } private Dictionary<string, object> _Tables;
    }
}
using System;
using Moq;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;

namespace LANDesk.Licensing.WebServices.Tests.Data
{
    public static class EntityFrameworkMockHelper
    {
        /// <summary>
        /// Returns a mock of a DbContext
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public static MockedDbContext<T> GetMockContext<T>() where T : DbContext
        {
            var instance = new MockedDbContext<T>();
            instance.MockTables();
            return instance;
        }

        /// <summary>
        /// Use this method to mock a table, which is a DbSet{T} oject, in Entity Framework.
        /// Leave the second list null if no adds or deletes are used.
        /// </summary>
        /// <typeparam name="T">The table data type</typeparam>
        /// <param name="table">A List{T} that is being use to replace a database table.</param>
        /// <returns></returns>
        public static DbSet<T> MockDbSet<T>(List<T> table) where T : class
        {
            var dbSet = new Mock<DbSet<T>>();
            dbSet.As<IQueryable<T>>().Setup(q => q.Provider).Returns(() => table.AsQueryable().Provider);
            dbSet.As<IQueryable<T>>().Setup(q => q.Expression).Returns(() => table.AsQueryable().Expression);
            dbSet.As<IQueryable<T>>().Setup(q => q.ElementType).Returns(() => table.AsQueryable().ElementType);
            dbSet.As<IQueryable<T>>().Setup(q => q.GetEnumerator()).Returns(() => table.AsQueryable().GetEnumerator());
            dbSet.Setup(set => set.Add(It.IsAny<T>())).Callback<T>(table.Add);
            dbSet.Setup(set => set.AddRange(It.IsAny<IEnumerable<T>>())).Callback<IEnumerable<T>>(table.AddRange);
            dbSet.Setup(set => set.Remove(It.IsAny<T>())).Callback<T>(t => table.Remove(t));
            dbSet.Setup(set => set.RemoveRange(It.IsAny<IEnumerable<T>>())).Callback<IEnumerable<T>>(ts =>
            {
                foreach (var t in ts) { table.Remove(t); }
            });
            return dbSet.Object;
        }

        /// <summary>
        /// Mocks all the DbSet{T} properties that represent tables in a DbContext.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="mockedContext"></param>
        public static void MockTables<T>(this MockedDbContext<T> mockedContext) where T : DbContext
        {
            Type contextType = typeof(T);
            var dbSetProperties = contextType.GetProperties().Where(prop => (prop.PropertyType.IsGenericType) && prop.PropertyType.GetGenericTypeDefinition() == typeof(DbSet<>));
            foreach (var prop in dbSetProperties)
            {
                var dbSetGenericType = prop.PropertyType.GetGenericArguments()[0];
                Type listType = typeof(List<>).MakeGenericType(dbSetGenericType);
                var listForFakeTable = Activator.CreateInstance(listType);
                var parameter = Expression.Parameter(contextType);
                var body = Expression.PropertyOrField(parameter, prop.Name);
                var lambdaExpression = Expression.Lambda<Func<T, object>>(body, parameter);
                var method = typeof(EntityFrameworkMockHelper).GetMethod("MockDbSet").MakeGenericMethod(dbSetGenericType);
                mockedContext.Setup(lambdaExpression).Returns(method.Invoke(null, new[] { listForFakeTable }));
                mockedContext.Tables.Add(prop.Name, listForFakeTable);
            }
        }
    }
}

15 Comments

  1. Praveena says:

    Sorry i am fresher.. where i have to use this code? in viewmodel class or testclass?
    MockDbContext = EntityFrameworkMockHelper.GetMockContext();
    MockDbContext.Object.People.Add(new Person{ FirstName = "John", LastName = "Doe" });
    MockedDbContext.Object.People.AddRange(SamplePeople);

  2. Manju says:

    mockDbContext.Object.Members.Add(member);
    threw exception:
    System.NotSupportedException: Invalid setup on a non-virtual

  3. DiegoPrz says:

    How would you mock the where clause of the dbSet?
    cause for me

    mockDbSet
        .Setup(set => set.Where(It.IsAny<Expression<Func>>()))
        .Returns<Expression<Func>>(t => queryableData.Where(t));
    

    is not working

  4. Darío says:

    Hi,

    Is there a way to mock linq with Include() using this code?

  5. Galio says:

    Hi, great help with these class, but i have a question, where "_Tables" is declared? i take your code but it is underlined red at "_Tables"

  6. Miroslav says:

    Great example. But in general developer needs to mock extension methods also: FirstOrDefault(), Single(), etc. The typical case is some service that checks whether exists concrete record and than make some logic. I believe that the best solution is real database mock.

    • Rhyous says:

      You said: "I believe that the best solution is real database mock."

      I disagree. You get 95% of your needs met with a code only mock and no actual database. The cost and time required to get that next 5% by doing a database mock, is massive. It is usually 10 times the work and has a huge maintenance burden.

      Also, I may be wrong, but isn't FirstOrDefault there as part of linq? Because this mock retuns List, aren't FirstOrDefault available for List without having to mock them? So don't you get exactly what you are asking for without doing more mocking and without the massive undertaking of a database mock?

  7. Nathan says:

    This is exactly what I needed; thank you!

    I created a method using generics to get NBuilder to crank out data, but I was struggling with implementing generics within the dbset and context.

    Thank you again, this is fantastic!

  8. Dario says:

    Hi,

    Thanks a lot for your work. It´s really a help! I must just figure out how to add a mock for BeginTransaction.

  9. Cyrus says:

    Thanks. The code is really helpful. However, using this helper I am unable to use

     mockdbSet.Verify(n => n.Add(people), Times.Exactly(1))
    

    . because I can't access to the mocked dbset. Can you help me on this? Sorry because i am not good with generic method and could not make it work.

Leave a Reply

How to post code in comments?

*