使用C#/ Moq Framework将输入参数模拟为异步DbContext函数

时间:2016-03-16 22:53:35

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

我制作了一个DbContext模拟并填充了测试数据。 DbSet是一个受保护的类,因此无法模拟结果,因此我找到了一个扩展方法。

    public static class DbSetMocking
    {

    private static Mock<DbSet<T>> CreateMockSet<T>(IQueryable<T> data)
            where T : class
    {
        var queryableData = data.AsQueryable();
        var mockSet = new Mock<DbSet<T>>();

        mockSet.As<IQueryable<T>>().Setup(m => m.Provider)
                .Returns(queryableData.Provider);
        mockSet.As<IQueryable<T>>().Setup(m => m.Expression)
                .Returns(queryableData.Expression);
        mockSet.As<IQueryable<T>>().Setup(m => m.ElementType)
                .Returns(queryableData.ElementType);
        mockSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator())
                .Returns(queryableData.GetEnumerator());

      return mockSet;
    }

    public static IReturnsResult<TContext> ReturnsDbSet<TEntity, TContext>(this IReturns<TContext, DbSet<TEntity>> setup, TEntity[] entities)
        where TEntity : class
        where TContext : DbContext
    {
        var mockSet = CreateMockSet(entities.AsQueryable());
        return setup.Returns(mockSet.Object);
    }

    public static IReturnsResult<TContext> ReturnsDbSet<TEntity, TContext>(this IReturns<TContext, DbSet<TEntity>> setup, IQueryable<TEntity> entities)
        where TEntity : class
        where TContext : DbContext
    {
        var mockSet = CreateMockSet(entities);
        return setup.Returns(mockSet.Object);
    }

    public static IReturnsResult<TContext> ReturnsDbSet<TEntity, TContext>(this IReturns<TContext, DbSet<TEntity>> setup, IEnumerable<TEntity> entities)
        where TEntity : class
        where TContext : DbContext
    {
        var mockSet = CreateMockSet(entities.AsQueryable());
        return setup.Returns(mockSet.Object);
    }
}

创建DbContext模拟:

var db = new Mock<DbContext>();

//Populate
this.db.Setup(x => x.MyObjects).ReturnsDbSet(
  new List<MyObject>
  {
     new MyObject{Id=1, Description="Test"},                   
  }
 );

其次,我正在尝试扩展Mocks以包含Find(id)和FindAsync(id)方法。 Theese方法正在DbSetMocking类中。查找方法工作正常:

 mockSet.Setup(m => m.Find(It.IsAny<object[]>()))
      .Returns<object[]>(id => StaticMethodtoFindStuff<T>(queryableData,   id));

但是,我无法使FindAsync方法起作用。这是我到目前为止所尝试的:

mockSet.Setup(m => m.FindAsync(It.IsAny<object[]>()))
   .Returns(Task.FromResult(StaticMethodtoFindStuff<T>(queryableData, 1)));

这个有效,但后来我无权访问该函数设置的参数。试过这个,编译工作正常,但它在执行时失败,出现错误msg:

类型的对象&#39; System.Object []&#39;无法转换为类型&#39; System.Threading.Tasks.Task`1 [System.Object []]&#39;。

mockSet.Setup(m => m.FindAsync(It.IsAny<object[]>()))
    .Returns<Task<object[]>>(d =>
     {
        return Task.FromResult(StaticMethodtoFindStuff<T>(queryableData, d));
     });

有人知道如何实现这个功能吗?

2 个答案:

答案 0 :(得分:1)

尝试更改此行:

mockSet.Setup(m => m.FindAsync(It.IsAny<object[]>()))
    .Returns<Task<object[]>>(d =>
     {
        return Task.FromResult(StaticMethodtoFindStuff<T>(queryableData, d));
     });

为:

mockSet.Setup(m => m.FindAsync(It.IsAny<object[]>()))
    .Returns<object[]>(d =>
     {
        return Task.FromResult(StaticMethodtoFindStuff<T>(queryableData, d));
     });

这里的问题是,T的泛型参数.Returns<T>不是结果的类型,而是{{>第一个参数的类型,在函数内传递在{ {1}}方法 - 用于它的对象[]。

查看Moq源代码以获取更多信息:

https://github.com/moq/moq4/blob/b2cf2d303ea6644fda2eaf10bad43c88c05b395f/Source/Language/IReturns.Generated.cs

以下是来源的引用,检查T1和T2参数评论:

.Setup

您还可以看到,它定义了具有可变数量的泛型参数的多个返回重载,因此您可以模拟最多包含16个参数的方法。

答案 1 :(得分:1)

终于搞清楚了。原来我在那里错过了一个'async'关键字。代码是:

        mockSet.Setup(m => m.FindAsync(It.IsAny<object[]>()))
            .Returns<object[]>(async (d) =>
            {
                return await Task.FromResult(StaticMethodtoFindStuff<T>(queryableData, d));
            });