Search code examples
c#unit-testingasynchronousxunit.netnsubstitute

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


I am attempting to test an async Task method that calls synchronous code before and after calling a separate async Task method. The synchronous code updates a loading state enumeration that tells the view layer what to display (e.g. a loading spinner when loading, an error message when an exception is thrown, etc.). Note that I have omitted the property changed event in this example for brevity.

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,
    }
}

I am using NSubstitute to mock IDataService, XUnit to run the tests, and Fluent Assertions to assert. I am able to test the loading state value in the following cases:

  1. When the LoadData() method completes successfully:
[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. When the LoadData() method throws an exception:
[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);
}

However, I cannot write a test that asserts that the loading state has been set to "Busy".

[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);
}

How do I write a test for the initial loading state? Is there a way to pause the call to LoadData() so that I can assert on the initial loading state?


Solution

  • You can mock LoadData to return a task managed by you, which will complete when you say so. Then, do not await Refresh because for this test you don't need for Refresh to complete, you want to check the state in progress. Then after you call Refresh (without await) the LoadData will be in progress and you can verify that state is Busy. Then complete the LoadData and wait for Refresh to complete to finish the test. For example:

    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;