【问题标题】:Second Service.AllAsync() returns empty ICollection, when mocking DbSet and ApplicationDbContext on EF6在 EF6 上模拟 DbSet 和 ApplicationDbContext 时,第二个 Service.AllAsync() 返回空 ICollection
【发布时间】:2014-09-01 15:35:03
【问题描述】:

我正在尝试使用Moq 测试方法DepartmentSerive.SearchAsync(string name)。到目前为止,我已经关注了这个 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&lt;TEntity&gt; 是一个通用服务,具有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() 正在返回元素一样,但在此过程中也会清空列表。 当我使用真实数据库时,不会发生此问题。我在DepartmentController 处写了两个连续的AllAsync(),第二个仍然返回正确的数据。

在模拟列表或设置测试时,我做错了吗?

【问题讨论】:

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


    【解决方案1】:

    我能够解决问题。事实证明,在与 Microsoft 的 internal class TestDbAsyncEnumerator&lt;T&gt; 实现中包含的 IEnumerator&lt;T&gt; _inner 交互后,Current 属性没有被重置,保持在最后一个位置,因此在第二次调用中被评估为 null await db.Set&lt;TEntity&gt;().ToListAsync();.

    所以,只需在TestDbAsyncEnumerator&lt;T&gt; 类的方法Dispose() 中添加_inner.Reset(); 即可解决问题!考虑到AsyncQueryProvider 没有实现Reset() 方法,并且在进行查询后也会调用Dispose() 方法,因此您必须将其放在try-catch 块中。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-08-24
      • 1970-01-01
      • 1970-01-01
      • 2014-10-23
      • 1970-01-01
      • 1970-01-01
      • 2019-10-06
      相关资源
      最近更新 更多