如何测试在异步任务方法之前调用的同步代码?

How do I test synchronous code called before an async Task method?

我正在尝试测试一个 async Task 方法,该方法在调用单独的 async Task 方法之前和之后调用同步代码。同步代码更新一个加载状态枚举,它告诉视图层要显示什么(例如加载时的加载微调器,抛出异常时的错误消息等)。请注意,为了简洁起见,我在此示例中省略了 属性 更改事件。

namespace Models
{
    public class DataRepository
    {
        private readonly IDataService dataService;

        public DataRepository(IDataService dataService)
        {
            this.dataService = dataService;
        }

        public LoadingState LoadingState { get; set; }

        public async Task Refresh()
        {
            // Synchronous code to notify view to show loading state
            this.LoadingState = LoadingState.Busy;

            try
            {
                await this.dataService.LoadData();

                // Synchronous code to notify view to show done state
                this.LoadingState = LoadingState.Done;
            }
            catch (Exception e)
            {
                // Synchronous code to notify view to show error state
                this.LoadingState = LoadingState.Error;
            }
        }
    }

    // This is in a separate class in the actual code. Including here for context.
    public enum LoadingState
    {
        Busy,
        Error,
        Done,
    }
}

我正在使用 NSubstitute to mock IDataService, XUnit to run the tests, and Fluent Assertions 断言。我可以在以下情况下测试加载状态值:

  1. LoadData() 方法成功完成时:
[Fact]
public async void Refresh_Success_ExpectLoadingStateDone()
{
    var mockDataService = Substitute.For<IDataService>();
    var dataRepository = new DataRepository(mockDataService);

    await dataRepository.Refresh();

    dataRepository.LoadingState.Should().Be(LoadingState.Done);
}
  1. LoadData()方法抛出异常时:
[Fact]
public async void Refresh_ThrowsException_ExpectLoadingStateError()
{
    var mockDataService = Substitute.For<IDataService>();
    mockDataService.Throws<Exception>();
    var dataRepository = new DataRepository(mockDataService);

    await dataRepository.Refresh();

    dataRepository.LoadingState.Should().Be(LoadingState.Error);
}

但是,我无法编写断言加载状态已设置为“忙碌”的测试。

[Fact]
public async void Refresh_InitialState_ExpectLoadingStateBusy()
{
    var mockDataService = Substitute.For<IDataService>();
    var dataRepository = new DataRepository(mockDataService);

    await dataRepository.Refresh();

    // This test fails because the loading state is set to "Done" at this point.
    dataRepository.LoadingState.Should().Be(LoadingState.Busy);
}

如何为初始加载状态编写测试?有没有办法暂停对 LoadData() 的调用,以便我可以断言初始加载状态?

您可以将 LoadData 模拟为 return 由您管理的任务,当您这样说时它就会完成。然后,不要 await Refresh 因为对于这个测试你不需要 Refresh 来完成,你想要检查正在进行的状态。然后在您调用 Refresh(没有 await)后,LoadData 将进行,您可以验证状态为 Busy。然后完成 LoadData 并等待 Refresh 完成以完成测试。例如:

var mockDataService = Substitute.For<IDataService>();
// this will represent our LoadData in progress
// it won't complete until we tell it
var inProgressTask = new TaskCompletionSource();
// mock LoadData to return this task
mockDataService.Configure().LoadData().Returns(inProgressTask.Task);
var dataRepository = new DataRepository(mockDataService);
// now, execute Refresh but do not await it yet
var pendingRefresh = dataRepository.Refresh();
// assert that state is Busy at this point, as it should be
var state = dataRepository.LoadingState; // Busy
// signal LoadData to complete
inProgressTask.SetResult();
// await Refresh
await pendingRefresh;