簡體   English   中英

如何正確編寫異步XUnit測試?

[英]How to correctly write async XUnit test?

我正在使用異步xUnit測試,我注意到不一致的傳遞行為:

public async Task FetchData()
{
    //Arrange
    var result = await arrangedService.FetchDataAsync().ConfigureAwait(false);
    //Assert
}

我已經完成了此測試執行的調用堆棧,並驗證了我的所有庫代碼都在每個任務之后調用.ConfigureAwait(false) 但是,盡管如此,在執行Run All時,此測試和其他測試將間歇性地失敗,但是當我在調試器上完成時,會通過斷言和手動檢查。 很明顯,我沒有正確地做某事。 我已經嘗試在測試本身中刪除對ConfigureAwait(false)的調用,以防有特殊的xUnit同步上下文,但它沒有改變任何東西。 以一致的方式測試異步代碼的最佳方法是什么?

編輯好的,這是我嘗試創建一個運行的代碼的超簡化示例,以提供正在發生的事情的示例:

using Graph = Microsoft.Azure.ActiveDirectory.GraphClient;

public async Task FetchData()
{
    var adUsers = baseUsers //IEnumerable<Graph.User>
        .Cast<Graph.IUser>()
        .ToList();
    var nextPageUsers = Enumerable
        .Range(GoodIdMin, GoodIdMax)
        .Select(number => new Graph.User
        {
            Mail = (-number).ToString()
        })
        .Cast<Graph.IUser>()
        .ToList();

    var mockUserPages = new Mock<IPagedCollection<Graph.IUser>>();
    mockUserPages
        .Setup(pages => pages.MorePagesAvailable)
        .Returns(true);
    mockUserPages
        .Setup(pages => pages.CurrentPage)
        .Returns(new ReadOnlyCollection<Graph.IUser>(adUsers));
    mockUserPages
        .Setup(pages => pages.GetNextPageAsync())
        .ReturnsAsync(mockUserPages.Object)
        .Callback(() =>
        {
            mockUserPages
                .Setup(pages => pages.CurrentPage)
                .Returns(new ReadOnlyCollection<Graph.IUser>(nextPageUsers));
            mockUserPages
                .Setup(pages => pages.MorePagesAvailable)
                .Returns(false);
        });

    var mockUsers = new Mock<Graph.IUserCollection>();
    mockUsers
        .Setup(src => src.ExecuteAsync())
        .ReturnsAsync(mockUserPages.Object);

    var mockGraphClient = new Mock<Graph.IActiveDirectoryClient>();
    mockGraphClient
        .Setup(src => src.Users)
        .Returns(mockUsers.Object);

    var mockDbUsers = CreateBasicMockDbSet(baseUsers.Take(10)
        .Select(user => new User
        {
            Mail = user.Mail
        })
        .AsQueryable());
    var mockContext = new Mock<MyDbContext>();
    mockContext
        .Setup(context => context.Set<User>())
        .Returns(mockDbUsers.Object);

    var mockGraphProvider = new Mock<IGraphProvider>(); 
    mockGraphProvider
        .Setup(src => src.GetClient()) //Creates an IActiveDirectoryClient
        .Returns(mockGraphClient.Object);

    var getter = new UserGetter(mockContext.Object, mockGraphProvider.Object);

    var result = await getter.GetData().ConfigureAwait(false);

    Assert.True(result.Success); //Not the actual assert
}

這是在var result = ... line上執行的代碼:

public UserGetterResult GetData()
{
    var adUsers = await GetAdUsers().ConfigureAwait(false);
    var dbUsers = Context.Set<User>().ToList(); //This is the injected context from before
    return new UserGetterResult //Just a POCO
    {
        AdUsers = adUsers
            .Except(/*Expression that indicates whether
             or not this user is in the database*/)
            .ProjectTo<User>()
            .ToList(),
        DbUsers = dbUsers.ProjectTo<User>().ToList() //Automapper 6.1.1
    };
}

private async Task<List<User>> GetAdUsers()
{
    var userPages = await client //Injected IActiveDirectoryClient from before
        .Users
        .ExecuteAsync()
        .ConfigureAwait(false);
    var users = userPages.CurrentPage.ToList();
    while(userPages.MorePagesAvailable)
    {
        userPages = await userPages.GetNextPageAsync().ConfigureAwait(false);
        users.AddRange(userPages.CurrentPage);
    }
    return users;
}

代碼的目的是獲取AD中但不是數據庫的用戶列表以及數據庫中的用戶列表。

編輯編輯因為我忘了在原始更新中包含它,所以在調用`IUserCollection.ExecuteAsync()時都會發生錯誤。

IUserCollection.ExecuteAsync()似乎是根據原始帖子中顯示的內容正確配置的。

現在關注以下方法......

private async Task<List<User>> GetAdUsers() {
    var userPages = await client //Injected IActiveDirectoryClient from before
        .Users
        .ExecuteAsync()
        .ConfigureAwait(false);
    var users = userPages.CurrentPage.ToList();
    while(userPages.MorePagesAvailable) {
        userPages = await userPages.GetNextPageAsync().ConfigureAwait(false);
        users.AddRange(userPages.CurrentPage);
    }
    return users;
}

我關心如何在模擬中設置用戶頁面。 鑒於GetAdUsers方法的流程,最好使用SetupSequence來模擬重復調用CurrentPageMorePagesAvailable

var mockUserPages = new Mock<IPagedCollection<Graph.IUser>>();
mockUserPages
    .SetupSequence(_ => _.MorePagesAvailable)
    .Returns(true) // First time called to enter while loop
    .Returns(false); // Second time called to exit while loop
mockUserPages
    .SetupSequence(_ => _.CurrentPage)
    .Returns(new ReadOnlyCollection<Graph.IUser>(adUsers)) // First time called to get List
    .Returns(new ReadOnlyCollection<Graph.IUser>(nextPageUsers)); // Second time called to get next page
mockUserPages
    .Setup(pages => pages.GetNextPageAsync())
    .ReturnsAsync(mockUserPages.Object); // No need for callback

參考Moq快速入門

我懷疑問題可能是執行回調和下一次請求mockUserPages.CurrentPage之間的延遲

嘗試分離用戶頁面集合:

var mockAdUserPages = new Mock<IPagedCollection<Graph.IUser>>();
    mockAdUserPages 
        .Setup(pages => pages.MorePagesAvailable)
        .Returns(true);
    mockAdUserPages 
        .Setup(pages => pages.CurrentPage)
        .Returns(new ReadOnlyCollection<Graph.IUser>(adUsers));

//Setup second page
var mockNextUserPages = new Mock<IPagedCollection<Graph.IUser>>();
mockNextUserPages 
        .Setup(pages => pages.MorePagesAvailable)
        .Returns(false);
    mockNextUserPages 
        .Setup(pages => pages.CurrentPage)
        .Returns(new ReadOnlyCollection<Graph.IUser>(nextPageUsers));

//Return next page
    mockAdUserPages 
        .Setup(pages => pages.GetNextPageAsync())
        .ReturnsAsync(mockNextUserPages.Object);

暫無
暫無

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

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