簡體   English   中英

使用C#/ Moq Framework使用輸入參數模擬異步DbContext函數

[英]Mocking async DbContext functions with input parameter with C#/Moq Framework

我做了一個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)方法。 這些方法位於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:

類型'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));
     });

有人知道如何實現此功能嗎?

嘗試更改此行:

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<T>通用參數T不是結果的類型,而是那是在.Setup方法中傳遞給函數第一個參數的類型-對於您的對象是[]。

查看Moq源代碼以獲取更多信息:

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個參數的方法。

終於弄明白了。 原來我在那里缺少一個“異步”關鍵字。 代碼是:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM