如何正确编写异步XUnit测试?

jok*_*der 6 c# asynchronous moq xunit

我正在使用异步xUnit测试,并且注意到不一致的传递行为:

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

我经历了此测试执行的调用堆栈,并验证了我的所有库代码.ConfigureAwait(false)在每个任务之后都在调用。但是,尽管如此,执行a时,该测试和其他测试都会间歇性地失败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
}
Run Code Online (Sandbox Code Playgroud)

这是正在执行的代码var result = ...

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;
}
Run Code Online (Sandbox Code Playgroud)

该代码的目的是获取AD中的用户列表,而不是数据库中的用户列表以及数据库中的用户列表。

编辑编辑因为我忘了,包括这在原来的更新,错误都发生在调用`IUserCollection.ExecuteAsync()。

Ama*_*n B 0

我怀疑问题可能是执行回调和下一个对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);
Run Code Online (Sandbox Code Playgroud)