21

I'm trying to mock DbContext and DbSet. This works for my previous unit tests, but problem occurs while my code was calling ToList method on DbSet second time.

First dbSet.ToList() returns mocked results. Second one returns 0 elements;

       var queryableData = new List<string>{ "a", "b", "c" }.AsQueryable();

        var mockDbSet = new Mock<DbSet<string>>();
        var q = mockDbSet.As<IQueryable<string>>();
        q.Setup(m => m.Provider).Returns(queryableData.Provider);
        q.Setup(m => m.Expression).Returns(queryableData.Expression);
        q.Setup(m => m.ElementType).Returns(queryableData.ElementType);
        q.Setup(m => m.GetEnumerator()).Returns(queryableData.GetEnumerator());

        DbSet<string> dbset = mockDbSet.Object;
        IQueryable<string> query = dbset;

        //RESULTS: abc
        var a1 = dbset.ToList();
        foreach (var a in a1)
            Console.Write(a);

        //NO RESULTS
        var a2 = dbset.ToList();
        foreach (var a in a2)
            Console.Write(a);
Emil Jasiński
  • 391
  • 4
  • 13

4 Answers4

45

You return the very same enumerator instance upon each call to GetEnumerator. When it enumerates once, it is done, EF doesn't call its Reset method, rather it asks for a new enumerator.

But you return the one that just has yielded all elements and yields no more.

Instead, return a function that returns the enumerator, that will return a new enumerator each time you ask for it.

 q.Setup(m => m.GetEnumerator()).Returns( () => queryableData.GetEnumerator() );
Wiktor Zychla
  • 47,367
  • 6
  • 74
  • 106
2

I just wanted to added to Wiktor Zychla's answer my little part. If someone is looking for Async version of this mock (coming from this tutorial: http://msdn.microsoft.com/en-us/data/dn314429.aspx#async) then this is my modification to TestDbAsyncEnumerator<T> class:

internal class TestDbAsyncEnumerator<T> : IDbAsyncEnumerator<T>
{
    private readonly IEnumerator<T> _inner;

    public TestDbAsyncEnumerator(IEnumerator<T> inner)
    {
        _inner = inner;
    }

    public TestDbAsyncEnumerator(Func<IEnumerator<T>> valueFunction)
    {
        _inner = valueFunction();
    }

    public void Dispose()
    {
        _inner.Dispose();
    }

    public Task<bool> MoveNextAsync(CancellationToken cancellationToken)
    {
        return Task.FromResult(_inner.MoveNext());
    }

    public T Current
    {
        get { return _inner.Current; }
    }

    object IDbAsyncEnumerator.Current
    {
        get { return Current; }
    }
}

Then like Wiktor suggested you have to setup it with delegate so in case of async it would be like that:

mockSet.As<IDbAsyncEnumerable<Blog>>()
            .Setup(m => m.GetAsyncEnumerator())
            .Returns(new TestDbAsyncEnumerator<Blog>(() => data.GetEnumerator()));

If someone wants source for that then here you go: https://github.com/kelostrada/EntityFrameworkWithMock.Test

Kelu Thatsall
  • 2,494
  • 1
  • 22
  • 50
  • The code in your GitHub repo doesn't match what you have in stackoverflow? Doesn't work in my case either way. – Ricardo stands with Ukraine Jan 27 '17 at 14:42
  • what's not working for you? I haven't looked at this code for more than 2 years, it might not work because of some package updates and stuff like that. Does it not compile? Besides that I found that trying to mock DbSets was like the most daunting task I ever approached... would never try that again. Just setup a new database for tests and set a transaction on each test - much more reliable and easy to use solution. (maybe only just a bit slower) – Kelu Thatsall Jan 28 '17 at 08:40
  • my comment is more about code here and in the repository you linked don't match. Your code builds, but doesn't fix similar problem have described here: http://stackoverflow.com/questions/41899177/mock-entity-framework-long-linq-query/41900708#41900708 . Understand now that mocking DbSets is a very hard way to go and maybe not best, thought EF was made to be testable, but maybe is not. Too late now to change things in project working on. Thanks. – Ricardo stands with Ukraine Jan 30 '17 at 09:35
2

I can't comment on Wiktor's post as I haven't got enough reputation, but i would like to add a little extra to Wiktor's answer.

The code

mockSet.Setup((m => m.GetEnumerator()).Returns(() => data.GetEnumerator())

Will fail if you give it a zero argument lambda like in the example(At least the version I am using). Passing an argument that is never used will allow it to meet the signature requirement however.

mockSet.Setup((m => m.GetEnumerator()).Returns(x => data.GetEnumerator())

Will compile however, and works as expected.

And this also applies to Kelu's answer as well (Although he has added the lambda in the wrong place.

mockSet.As<IDbAsyncEnumerable<Blog>>()
        .Setup(m => m.GetAsyncEnumerator())
        .Returns(x => new TestDbAsyncEnumerator<Blog>(data.GetEnumerator()));

Not trying to nitpick answers here, i'm just adding because i made these mistakes myself :)

Andrew Monteith
  • 117
  • 1
  • 3
1

If you put a "Where" clause before the .ToList() call, the data should remain present.

var a1 = dbset.Where(m => m != null).ToList();

var a2 = dbset.Where(m => m != null).ToList();
Ryan Penfold
  • 752
  • 2
  • 11
  • 16