【问题标题】:Mocking async DbContext functions with input parameter with C#/Moq Framework使用 C#/Moq 框架模拟带有输入参数的异步 DbContext 函数
【发布时间】:2016-03-16 22:53:35
【问题描述】:

我制作了一个 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 的 Mock:

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) 方法。这些方法被放置在 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)));

这个可行,但是我无法访问该函数设置的参数。试过这个,编译工作正常,但执行失败并显示错误消息:

“System.Object[]”类型的对象无法转换为“System.Threading.Tasks.Task`1[System.Object[]]”类型。

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

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

【问题讨论】:

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


    【解决方案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));
         });
    

    这里的问题是,.Returns&lt;T&gt; 的通用参数 T 不是结果的类型,而是.Setup 方法中的第一个参数的类型,在函数内部传递 - - 为你的它的对象[]。

    查看起订量源代码了解更多信息:

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

    这是来自来源的引用,请查看 T1 和 T2 参数注释:

    /// <summary>
    /// Specifies a function that will calculate the value to return from the method, 
    /// retrieving the arguments for the invocation.
    /// </summary>
    /// <typeparam name="T1">The type of the first argument of the invoked method.</typeparam>
    /// <typeparam name="T2">The type of the second argument of the invoked method.</typeparam>
    /// <param name="valueFunction">The function that will calculate the return value.</param>
    /// <return>Returns a calculated value which is evaluated lazily at the time of the invocation.</return>
    /// <example>
    /// <para>
    /// The return value is calculated from the value of the actual method invocation arguments. 
    /// Notice how the arguments are retrieved by simply declaring them as part of the lambda 
    /// expression:
    /// </para>
    /// <code>
    /// mock.Setup(x => x.Execute(
    ///                      It.IsAny<int>(), 
    ///                      It.IsAny<int>()))
    ///     .Returns((int arg1, int arg2) => arg1 + arg2); //I fixed that line, it's different in the documentation and is incorrect
    /// </code>
    /// </example>
    IReturnsResult<TMock> Returns<T1, T2>(Func<T1, T2, TResult> valueFunction);
    

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

    【讨论】:

      【解决方案2】:

      终于弄明白了。原来我在那里遗漏了一个“异步”关键字。代码是:

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

      【讨论】:

        猜你喜欢
        • 2019-03-13
        • 2019-01-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-12-11
        • 1970-01-01
        • 2011-11-16
        • 2023-04-11
        相关资源
        最近更新 更多