Second Service.AllAsync() returns empty ICollection, when mocking DbSet and ApplicationDbContext on EF6

asp.net-mvc-5 c# entity-framework-6 mocking unit-testing

Question

I'm trying to test the method DepartmentSerive.SearchAsync(string name) using Moq. So far, I've followed this msdn tutorial about how to mock DbSet and ApplicationDbContext classes.

First, I copied DbAsyncQueryProvider implementation, so I could use the asynchronous methods.

Then I implemented my DepartmentService : Service<TEntity> class.

public class DepartmentService : Service<Department>
{
    public DepartmentService(ApplicationDbContext db) : base(db) { }

    public async Task<ICollection<Department>> SearchAsync(string name)
    {
        if (String.IsNullOrEmpty(name))
        {
            return await AllAsync();
        }

        return await db.Departments
            .Where(d => d.Name.ToLower().Contains(name.ToLower()))
            .OrderBy(d => d.Name)
            .ToListAsync();
    }
}

Service<TEntity> is a generic service with methods like AllSync, Insert, Update, Delete, but the relevant part is here:

public abstract class Service<TEntity> where TEntity : class
{
    public virtual async Task<ICollection<TEntity>> AllAsync()
    {
        return await db.Set<TEntity>().ToListAsync();
    }
}

I have a Setup method as this:

[TestInitialize]
public void Setup()
{
    var data = new List<Department>
    {
        new Department { Id = 1, Name = "Computer Science" },
        new Department { Id = 2, Name = "Political Science" },
        new Department { Id = 3, Name = "Physics" },
        new Department { Id = 4, Name = "Mathematics" },
    }.AsQueryable();

    var set = new Mock<DbSet<Department>>();
    set.As<IDbAsyncEnumerable<Department>>().Setup(m => m.GetAsyncEnumerator()).Returns(new TestDbAsyncEnumerator<Department>(data.GetEnumerator()));
    set.As<IQueryable<Department>>().Setup(m => m.Provider).Returns(new TestDbAsyncQueryProvider<Department>(data.Provider));
    set.As<IQueryable<Department>>().Setup(m => m.Expression).Returns(data.Expression);
        set.As<IQueryable<Department>>().Setup(m => m.ElementType).Returns(data.ElementType);
    set.As<IQueryable<Department>>().Setup(m => m.GetEnumerator()).Returns(data.GetEnumerator());

    context = new Mock<ApplicationDbContext>();
    context.Setup(c => c.Set<Department>()).Returns(set.Object);
    context.Setup(c => c.Departments).Returns(set.Object);
}

And finally my test:

[TestMethod]
public async Task SearchWithEmptyString()
{
    var service = new DepartmentService(context.Object);
    var result = await service.SearchAsync(null);
    Assert.AreEqual(4, result.Count);

    result = await service.SearchAsync("");
    Assert.AreEqual(4, result.Count);
}

My test should pass, considering null and "" will be handled in the same way by SearchAsync(), but it's actually failing on the second Assert.AreEqual(4, result.Count);. For some reason, the first call to AllAsync() in the service returns the entire list, but the second one returns an empty one, as if AllAsync() were returning the elements, but also emptying the list in the process. This problem doesn't happen when I'm using the real database. I wrote two sequential AllAsync() at DepartmentController and the second one still returns the correct data.

Am I doing something wrong, when mocking the list, or setting up the test?

1
2
9/1/2014 3:35:03 PM

Accepted Answer

I was able to solve the problem. Turns out that after interacting with the IEnumerator<T> _inner contained in Microsoft's implementation for internal class TestDbAsyncEnumerator<T>, the property Current wasn't being reset, staying in the last position and, therefore, being evaluated as null in the second call for await db.Set<TEntity>().ToListAsync();.

So, just adding _inner.Reset(); in the method Dispose() of the class TestDbAsyncEnumerator<T> solves the problem! You have to put it inside a try-catch block, considering AsyncQueryProvider does not implement the Reset() method and the method Dispose() is also called after making a query.

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

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

    public void Dispose()
    {
        try
        {
            _inner.Reset();
        }
        catch (NotSupportedException)
        {
            //
        }
        _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; }
    }
}
1
10/12/2014 9:15:12 PM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow