在EF6上模拟DbSet和ApplicationDbContext时,第二个Service.AllAsync()返回空ICollection

时间:2014-09-01 15:35:04

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

我正在尝试使用DepartmentSerive.SearchAsync(string name)测试方法Moq。到目前为止,我已经按照msdn tutorial了解了如何模拟DbSetApplicationDbContext类。

首先,我复制了DbAsyncQueryProvider实现,因此我可以使用异步方法。

然后我实施了我的DepartmentService : Service<TEntity>课程。

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>是一项通用服务,其中包含AllSyncInsertUpdateDelete等方法,但相关部分位于此处:

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

我有一个Setup方法:

[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);
}

最后我的测试:

[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);
}

我的测试应该通过,考虑null""将由SearchAsync()以相同的方式处理,但它实际上在第二个Assert.AreEqual(4, result.Count);上失败。出于某种原因,服务中对AllAsync()的第一次调用返回整个列表,但第二次返回一个空列表,就像AllAsync()返回元素一样,但也清空了进程中的列表。 当我使用真实数据库时,不会发生此问题。我在AllAsync()写了两个顺序DepartmentController,第二个仍然返回正确的数据。

我做错了什么,嘲笑列表或设置测试?

1 个答案:

答案 0 :(得分:1)

我能够解决问题。事实证明,在与IEnumerator<T> _inner的Microsoft实现中包含的internal class TestDbAsyncEnumerator<T>进行交互后,属性Current未被重置,保持在最后位置,因此被评估为{{ 1}}在null的第二次调用中。

因此,只需在类await db.Set<TEntity>().ToListAsync();的方法_inner.Reset();中添加Dispose()即可解决问题!您必须将其放在try-catch块中,考虑TestDbAsyncEnumerator<T>未实现AsyncQueryProvider方法,并且在进行查询后也会调用方法Reset()

Dispose()