100

I'm trying to create a unit test for my service with a mocked DbContext. I created an interface IDbContext with the following functions:

public interface IDbContext : IDisposable
{
    IDbSet<T> Set<T>() where T : class;
    DbEntityEntry<T> Entry<T>(T entity) where T : class;
    int SaveChanges();
}

My real context implements this interface IDbContext and DbContext.

Now I'm trying to mock the IDbSet<T> in the context, so it returns a List<User> instead.

[TestMethod]
public void TestGetAllUsers()
{
    // Arrange
    var mock = new Mock<IDbContext>();
    mock.Setup(x => x.Set<User>())
        .Returns(new List<User>
        {
            new User { ID = 1 }
        });

    UserService userService = new UserService(mock.Object);

    // Act
    var allUsers = userService.GetAllUsers();

    // Assert
    Assert.AreEqual(1, allUsers.Count());
}

I always get this error on .Returns:

The best overloaded method match for
'Moq.Language.IReturns<AuthAPI.Repositories.IDbContext,System.Data.Entity.IDbSet<AuthAPI.Models.Entities.User>>.Returns(System.Func<System.Data.Entity.IDbSet<AuthAPI.Models.Entities.User>>)'
has some invalid arguments
Gaui
  • 8,723
  • 16
  • 64
  • 91
  • 2
    Although this post will be useful , I think it would be more-so if you included the implementation of the Moq DbContext , thanks for the idea . – LostNomad311 Jun 08 '18 at 15:50

6 Answers6

49

I managed to solve it by creating a FakeDbSet<T> class that implements IDbSet<T>

public class FakeDbSet<T> : IDbSet<T> where T : class
{
    ObservableCollection<T> _data;
    IQueryable _query;

    public FakeDbSet()
    {
        _data = new ObservableCollection<T>();
        _query = _data.AsQueryable();
    }

    public virtual T Find(params object[] keyValues)
    {
        throw new NotImplementedException("Derive from FakeDbSet<T> and override Find");
    }

    public T Add(T item)
    {
        _data.Add(item);
        return item;
    }

    public T Remove(T item)
    {
        _data.Remove(item);
        return item;
    }

    public T Attach(T item)
    {
        _data.Add(item);
        return item;
    }

    public T Detach(T item)
    {
        _data.Remove(item);
        return item;
    }

    public T Create()
    {
        return Activator.CreateInstance<T>();
    }

    public TDerivedEntity Create<TDerivedEntity>() where TDerivedEntity : class, T
    {
        return Activator.CreateInstance<TDerivedEntity>();
    }

    public ObservableCollection<T> Local
    {
        get { return _data; }
    }

    Type IQueryable.ElementType
    {
        get { return _query.ElementType; }
    }

    System.Linq.Expressions.Expression IQueryable.Expression
    {
        get { return _query.Expression; }
    }

    IQueryProvider IQueryable.Provider
    {
        get { return _query.Provider; }
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return _data.GetEnumerator();
    }

    IEnumerator<T> IEnumerable<T>.GetEnumerator()
    {
        return _data.GetEnumerator();
    }
}

Now my test looks like this:

[TestMethod]
public void TestGetAllUsers()
{
    //Arrange
    var mock = new Mock<IDbContext>();
    mock.Setup(x => x.Set<User>())
        .Returns(new FakeDbSet<User>
        {
            new User { ID = 1 }
        });

    UserService userService = new UserService(mock.Object);

    // Act
    var allUsers = userService.GetAllUsers();

    // Assert
    Assert.AreEqual(1, allUsers.Count());
}
Gaui
  • 8,723
  • 16
  • 64
  • 91
  • I think it is good to consider @Ladislav Mrnk's comments here http://stackoverflow.com/questions/6904139/fake-dbcontext-of-entity-framework-4-1-to-test?lq=1 and http://stackoverflow.com/questions/6766478/unit-testing-dbcontext – user8128167 Sep 22 '15 at 16:03
  • Trying to implement this under .net Core 1.0, but a major monkey wrench is that IDbSet has been removed, and the constructor is private so I can't even extract my own interface. – Paul Gorbas May 24 '17 at 21:25
  • This is no longer the preferred way in EF6 because EF6 added new changes to DbSet that aren't reflected in the IDbSet (and as above it was removed in Core). https://entityframework.codeplex.com/wikipage?title=Design%20Meeting%20Notes%20-%20May%2016%2c%202013 DbSet is instead more mockable, though I'm not sure yet what the proper implementation looks like. – IronSean Jan 02 '18 at 17:31
  • @PaulGorbas I found it easier to set up a DbContext with SqlLite than with Moq in .net core 2, e.g. https://gist.github.com/mikebridge/a1188728a28f0f53b06fed791031c89d. – mikebridge Feb 20 '18 at 20:40
  • For similar Fake for the `async` EF overloads, [see here](https://stackoverflow.com/a/27485675/314291) – StuartLC May 21 '18 at 21:22
  • You also could return not `FakeDbSet`, but other `Mock` of `IDbSet`. – realsonic Sep 10 '19 at 07:10
34

In case anyone is still interested, I was having the same problem and found this article very helpful: Entity Framework Testing with a Mocking Framework (EF6 onwards)

It only applies to Entity Framework 6 or newer, but it covers everything from simple SaveChanges tests to async query testing all using Moq (and a few of manual classes).

eitamal
  • 702
  • 8
  • 11
23

Thank you Gaui for your great idea =)

I did add some improvements to your solution and want to share it.

  1. My FakeDbSet also inherents from DbSet to get additional methods like AddRange()
  2. I replaced the ObservableCollection<T> with List<T> to pass all the already implemented methods in List<> up to my FakeDbSet

My FakeDbSet:

    public class FakeDbSet<T> : DbSet<T>, IDbSet<T> where T : class {
    List<T> _data;

    public FakeDbSet() {
        _data = new List<T>();
    }

    public override T Find(params object[] keyValues) {
        throw new NotImplementedException("Derive from FakeDbSet<T> and override Find");
    }

    public override T Add(T item) {
        _data.Add(item);
        return item;
    }

    public override T Remove(T item) {
        _data.Remove(item);
        return item;
    }

    public override T Attach(T item) {
        return null;
    }

    public T Detach(T item) {
        _data.Remove(item);
        return item;
    }

    public override T Create() {
        return Activator.CreateInstance<T>();
    }

    public TDerivedEntity Create<TDerivedEntity>() where TDerivedEntity : class, T {
        return Activator.CreateInstance<TDerivedEntity>();
    }

    public List<T> Local {
        get { return _data; }
    }

    public override IEnumerable<T> AddRange(IEnumerable<T> entities) {
        _data.AddRange(entities);
        return _data;
    }

    public override IEnumerable<T> RemoveRange(IEnumerable<T> entities) {
        for (int i = entities.Count() - 1; i >= 0; i--) {
            T entity = entities.ElementAt(i);
            if (_data.Contains(entity)) {
                Remove(entity);
            }
        }

        return this;
    }

    Type IQueryable.ElementType {
        get { return _data.AsQueryable().ElementType; }
    }

    Expression IQueryable.Expression {
        get { return _data.AsQueryable().Expression; }
    }

    IQueryProvider IQueryable.Provider {
        get { return _data.AsQueryable().Provider; }
    }

    IEnumerator IEnumerable.GetEnumerator() {
        return _data.GetEnumerator();
    }

    IEnumerator<T> IEnumerable<T>.GetEnumerator() {
        return _data.GetEnumerator();
    }
}

It is very easy to modify the dbSet and Mock the EF Context Object:

    var userDbSet = new FakeDbSet<User>();
    userDbSet.Add(new User());
    userDbSet.Add(new User());

    var contextMock = new Mock<MySuperCoolDbContext>();
    contextMock.Setup(dbContext => dbContext.Users).Returns(userDbSet);

Now it is possible to execute Linq queries, but be a aware that foreign key references may not be created automatically:

    var user = contextMock.Object.Users.SingeOrDefault(userItem => userItem.Id == 42);

Because the context object is mocked the Context.SaveChanges() won't do anything and property changes of your entites might not be populated to your dbSet. I solved this by mocking my SetModifed() method to populate the changes.

szuuuken
  • 896
  • 10
  • 12
  • Trying to implement this under .net Core 1.0, but a major monkey wrench is that IDbSet has been removed, and the constructor is private so I can't even extract my own interface. – Paul Gorbas May 24 '17 at 21:24
  • 1
    This answer solved my issue with mocking the EF .Add() function in ASP.NET MVC 5. Although I know it's not related to Core, I was able to extract enough info to inherit an existing class that mocks the dbset with dbset, idbset and override the Add method. Thanks! – CSharpMinor Feb 05 '19 at 18:59
  • MySuperCoolDbContext is this a concreate class ? – Jaydeep Shil Nov 06 '20 at 07:47
  • MySuperCoolDbContext is the name of your DbContext – szuuuken Nov 06 '20 at 16:49
11

Based on this MSDN article, I've created my own libraries for mocking DbContext and DbSet:

  • EntityFrameworkMock - GitHub
  • EntityFrameworkMockCore - GitHub

Both available on NuGet and GitHub.

The reason I've created these libraries is because I wanted to emulate the SaveChanges behavior, throw a DbUpdateException when inserting models with the same primary key and support multi-column/auto-increment primary keys in the models.

In addition, since both DbSetMock and DbContextMock inherit from Mock<DbSet> and Mock<DbContext>, you can use all features of the Moq framework.

Next to Moq, there also is an NSubstitute implementation.

Usage with the Moq version looks like this:

public class User
{
    [Key, Column(Order = 0)]
    public Guid Id { get; set; }

    public string FullName { get; set; }
}

public class TestDbContext : DbContext
{
    public TestDbContext(string connectionString)
        : base(connectionString)
    {
    }

    public virtual DbSet<User> Users { get; set; }
}

[TestFixture]
public class MyTests
{
    var initialEntities = new[]
        {
            new User { Id = Guid.NewGuid(), FullName = "Eric Cartoon" },
            new User { Id = Guid.NewGuid(), FullName = "Billy Jewel" },
        };
        
    var dbContextMock = new DbContextMock<TestDbContext>("fake connectionstring");
    var usersDbSetMock = dbContextMock.CreateDbSetMock(x => x.Users, initialEntities);
    
    // Pass dbContextMock.Object to the class/method you want to test
    
    // Query dbContextMock.Object.Users to see if certain users were added or removed
    // or use Mock Verify functionality to verify if certain methods were called: usersDbSetMock.Verify(x => x.Add(...), Times.Once);
}
geisterfurz007
  • 5,292
  • 5
  • 33
  • 54
huysentruitw
  • 27,376
  • 9
  • 90
  • 133
7

If anyone is still looking for answers I've implemented a small library to allow mocking DbContext.

step 1

Install Coderful.EntityFramework.Testing nuget package:

Install-Package Coderful.EntityFramework.Testing

step 2

Then create a class like this:

internal static class MyMoqUtilities
{
    public static MockedDbContext<MyDbContext> MockDbContext(
        IList<Contract> contracts = null,
        IList<User> users = null)
    {
        var mockContext = new Mock<MyDbContext>();

        // Create the DbSet objects.
        var dbSets = new object[]
        {
            MoqUtilities.MockDbSet(contracts, (objects, contract) => contract.ContractId == (int)objects[0] && contract.AmendmentId == (int)objects[1]),
            MoqUtilities.MockDbSet(users, (objects, user) => user.Id == (int)objects[0])
        };

        return new MockedDbContext<SourcingDbContext>(mockContext, dbSets); 
    }
}

step 3

Now you can create mocks super easily:

// Create test data.
var contracts = new List<Contract>
{
    new Contract("#1"),
    new Contract("#2")
};

var users = new List<User>
{
    new User("John"),
    new User("Jane")
};

// Create DbContext with the predefined test data.
var dbContext = MyMoqUtilities.MockDbContext(
    contracts: contracts,
    users: users).DbContext.Object;

And then use your mock:

// Create.
var newUser = dbContext.Users.Create();

// Add.
dbContext.Users.Add(newUser);

// Remove.
dbContext.Users.Remove(someUser);

// Query.
var john = dbContext.Users.Where(u => u.Name == "John");

// Save changes won't actually do anything, since all the data is kept in memory.
// This should be ideal for unit-testing purposes.
dbContext.SaveChanges();

Full article: http://www.22bugs.co/post/Mocking-DbContext/

niaher
  • 9,460
  • 7
  • 67
  • 86
  • 1
    Guess you are not maintaining the package to work with .net core. here is the error I got trying to install it: Package Coderful.EntityFramework.Testing 1.5.1 is not compatible with netcoreapp1.0 – Paul Gorbas May 24 '17 at 20:17
  • @PaulGorbas you're right, the lib is not updated for .net core. Are you suing it with EF Core? – niaher May 25 '17 at 11:50
  • 1
    My xUnit test project targets framework .NetCoreApp 1.0 aka EF 7 b4 they changed the namining convention – Paul Gorbas May 25 '17 at 20:12
5

I'm late, but found this article helpful: Testing with InMemory (MSDN Docs).

It explains how to use an in memory DB context (which is not a database) with the benefit of very little coding and the opportunity to actually test your DBContext implementation.

Quality Catalyst
  • 6,531
  • 8
  • 38
  • 62
  • 1
    Beware that InMemory database is not a relational database by default, so testing queries is discouraged. Sql-Lite in memory provider [may be better suited](https://learn.microsoft.com/en-us/ef/core/providers/in-memory/?tabs=dotnet-core-cli). – Ross Brasseaux Dec 04 '22 at 23:10