【问题标题】:Testing EF async methods with sync methods with MOQ使用 MOQ 同步方法测试 EF 异步方法
【发布时间】:2015-02-13 12:42:04
【问题描述】:

我有这个方法:

    public async Task DeleteUserAsync(Guid userId)
    {
        using (var context = this.contextFactory.Create())
        {
            var user = await context.Users.FirstOrDefaultAsync(x => x.Id.Equals(userId));

            if (user == null)
            {
                throw new Exception("User doesn't exist");
            }

            context.Users.Remove(user);

            await context.SaveChangesAsync();
        }
    }

我想测试一下。所以我创建了测试:

    [TestMethod]
    public async Task DeleteUsersSuccessfulCallTest()
    {
        // Arrange
        var id = Guid.NewGuid();
        var user = new User() { Id = id };

        var context = new Mock<IDashboardContext>();
        var usersDbSet = DbSetQueryMocking.GenericSetupAsyncQueryableMockInterfaceSet(new List<User> { user }.AsQueryable());
        context.Setup(x => x.Users).Returns(usersDbSet.Object);

        context.Setup(x => x.Users.Remove(user)).Returns(user).Verifiable();
        context.Setup(x => x.SaveChangesAsync()).ReturnsAsync(1).Verifiable();

        this.contextFactory.Setup(x => x.Create()).Returns(context.Object);

        // Act
        await this.userService.DeleteUserAsync(id);

        context.VerifyAll();
    }
}

我有这个方法来为我创建一个模拟集:

    public static Mock<DbSet<T>> GenericSetupAsyncQueryableMockSet<T>(IQueryable<T> data) where T : class
    {
        var mockSet = new Mock<DbSet<T>>();
        mockSet.As<IDbAsyncEnumerable<T>>().Setup(m => m.GetAsyncEnumerator()).Returns(new TestDbAsyncEnumerator<T>(data.GetEnumerator()));
        mockSet.As<IQueryable<T>>().Setup(m => m.Provider).Returns(new TestDbAsyncQueryProvider<T>(data.Provider));
        mockSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(data.Expression);
        mockSet.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(data.ElementType);
        mockSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(data.GetEnumerator());

        return mockSet;
    }

但是,由于我的 DeleteUserAsync 包含异步扩展方法和标准同步方法,我收到以下错误消息:

System.InvalidOperationException:源 IQueryable 的提供程序未实现 IDbAsyncQueryProvider。只有实现 IDbAsyncQueryProvider 的提供程序才能用于实体框架异步操作。更多详情请见http://go.microsoft.com/fwlink/?LinkId=287068

显然,如果我只是设置 DbSet&lt;T&gt; 并模拟出 Queryable,那么它会抛出相同的异常。

仅供参考:违规行是:

context.Setup(x => x.Users.Remove(user)).Returns(user).Verifiable();

这一行:错误

没有它:一个成功的测试。

我该如何解决这个问题?

【问题讨论】:

标签: c# entity-framework unit-testing asynchronous moq


【解决方案1】:

也许这篇文章来得有点晚,但无论如何:)

我推荐你使用这个库: https://www.nuget.org/packages/EntityFrameworkTesting/

这里是 GitHub 项目: https://github.com/scott-xu/EntityFramework.Testing

请记住将您的所有 DbSet 虚拟化

如果您想使用 FindAsync,您需要在“SetupData”方法中提供“Func”委托

例如:

var mockDbSetDisplays = new Mock<DbSet<DbEntity>>().SetupData(dataSourceDisplays, this.FindDisplay);

.....

private Display FindDisplay(object[] arg)
{
  return this.dataSourceDisplays.FirstOrDefault(x=> x.DisplayId == arg[0] as int?);
}

在这里你可以看到这个库如何模拟 FindAsync:

https://github.com/scott-xu/EntityFramework.Testing/blob/master/src/EntityFramework.Testing.Moq/MoqDbSetExtensions.cs

希望这对某人有用!

【讨论】:

    【解决方案2】:

    .AsQueryable() 生成的EnumerableQuery&lt;T&gt; 类没有实现IDbAsyncQueryProvider,但很容易通过实现扩展EnumerableQuery&lt;T&gt;。创建其中之一,而不是调用 .AsQueryable() 来包装您的收藏。我在下面有一个实现,可以将其进一步扩展到IDbSet&lt;T&gt;,但您可能不需要走那么远。

    class StubSet<T> : EnumerableQuery<T>, IDbSet<T>, IDbAsyncQueryProvider
        where T : class
    {
        public StubSet(IEnumerable<T> collection) : base(collection)
        {
            Local = new ObservableCollection<T>(collection);
        }
    
        public ObservableCollection<T> Local { get; private set; }
    
        public T Find(params object[] keyValues)
        {
            throw new NotImplementedException();
        }
    
        public T Add(T entity)
        {
            Local.Add(entity);
            return entity;
        }
    
        public T Remove(T entity)
        {
            Local.Remove(entity);
            return entity;
        }
    
        public T Attach(T entity)
        {
            return Add(entity);
        }
    
        public T Create()
        {
            throw new NotImplementedException();
        }
    
        public TDerivedEntity Create<TDerivedEntity>() where TDerivedEntity : class, T
        {
            throw new NotImplementedException();
        }
    
        public void DeleteObject(T entity)
        {
            throw new NotImplementedException();
        }
    
        public void Detach(T entity)
        {
            throw new NotImplementedException();
        }        
    
        async Task<object> IDbAsyncQueryProvider.ExecuteAsync(Expression expression, CancellationToken cancellationToken)
        {
            return ((IQueryProvider)this).Execute(expression);
        }
    
        async Task<TResult> IDbAsyncQueryProvider.ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken)
        {
            return ((IQueryProvider)this).Execute<TResult>(expression);
        }
    }
    

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-01-18
    • 1970-01-01
    • 2017-04-23
    • 2021-09-19
    • 2020-12-19
    • 1970-01-01
    • 2021-06-08
    • 1970-01-01
    相关资源
    最近更新 更多