Table of Contents

Awaiting an asynchronous state change

A test can fail if a component performs asynchronous renders. This may be due to a reason such as waiting for an asynchronous operation to complete before continuing its render life-cycle. For example, if a component is waiting for an async web service to return data to it in the OnInitializedAsync() life-cycle method before rendering it to the render tree.

You need to handle this specifically in your tests because tests execute in the test framework's synchronization context and the test renderer executes renders in its own synchronization context. If you do not, you will likely experience tests that sometimes pass and sometimes fail.

bUnit comes with several methods that help to deal with this issue: the WaitForState() method, the WaitForAssertion() method covered on the Assertion of asynchronous changes page, and the component-specific waiting methods WaitForComponent() and WaitForComponents() that are covered later on this page.

Let's start by taking a look at the WaitForState method in more detail.

Waiting for state using WaitForState

The WaitForState(Func<Boolean>, TimeSpan?) method can be used to block and wait in a test method, until the provided predicate returns true or the timeout is reached. (The default timeout is one second.)

Note

The WaitForState() method will try the predicate passed to it when the WaitForState() method is called, and every time the component under test renders.

Let us look at an example. Consider the following <AsyncData> component which awaits an async TextService in its OnInitializedAsync() life-cycle method. When the service returns the data, the component will automatically re-render to update its rendered markup:

<p>@text</p>

@code
{
  string text = string.Empty;
  [Parameter] public Task<string> TextService { get; set; }

  protected override async Task OnInitializedAsync()
  {
    text = await TextService;
  }
}

To test the <AsyncData> component, do the following:

var textService = new TaskCompletionSource<string>();
var cut = Render<AsyncData>(parameters => parameters
  .Add(p => p.TextService, textService.Task)
);

// Act - set the awaited result from the text service
textService.SetResult("Hello World");

// Wait for state before continuing test
cut.WaitForState(() => cut.Find("p").TextContent == "Hello World");

// Assert - verify result has been set
cut.MarkupMatches("<p>Hello World</p>");

This is what happens in the test:

  1. The test uses a TaskCompletionSource<string> to simulate an async web service.
  2. In the second highlighted line, the result is provided to the component through the textService. This causes the component to re-render.
  3. In the third highlighted line, the WaitForState() method is used to block the test until the predicate provided to it returns true.
  4. Finally, the tests assertion step can execute, knowing that the desired state has been reached.
Warning

The wait predicate and an assertion should not verify the same thing. Instead, use the WaitForAssertion(...) method covered on the Assertion of asynchronous changes page instead.

Controlling wait timeout

The timeout, which defaults to one second, can be controlled by passing a TimeSpan as the second argument to the WaitForState() method, e.g.:

cut.WaitForState(() => cut.Find("p").TextContent == "Long time", TimeSpan.FromSeconds(2));

If the timeout is reached, a WaitForFailedException exception is thrown with the following error message:

The state predicate did not pass before the timeout period passed.

Waiting for components using WaitForComponent and WaitForComponents

bUnit provides specialized methods for waiting for child components to appear in the render tree. These methods are useful when testing scenarios where components are rendered asynchronously based on some state change or data loading.

Waiting for a single component

The WaitForComponent<TComponent>(TimeSpan?) method waits until the specified component type is rendered in the DOM and returns the first instance found.

Consider the following <AsyncComponentLoader> component that loads child components asynchronously:

@if (isLoading)
{
    <p>Loading...</p>
}
else
{
    @foreach (var item in Items)
    {
        <ListItem Value="@item" />
    }
}

@code {
    [Parameter] public List<string> Items { get; set; } = new();

    private bool isLoading = true;

    protected override async Task OnInitializedAsync()
    {
        // Simulate async loading
        await Task.Delay(100);
        isLoading = false;
    }
}

To test that the <ListItem> components are rendered correctly, the WaitForComponent<TComponent>() method can be used:

var cut = Render<AsyncComponentLoader>(parameters => parameters
    .Add(p => p.Items, ["Item 1"]));

var listItem = cut.WaitForComponent<ListItem>();

Assert.Equal("Item 1", listItem.Find(".list-item").TextContent);

Waiting for multiple components

The WaitForComponents<TComponent>(TimeSpan?) method waits until at least one instance of the specified component type is rendered in the DOM and returns all instances found:

var items = new List<string> { "Item 1", "Item 2", "Item 3" };
var cut = Render<AsyncComponentLoader>(parameters => parameters
    .Add(p => p.Items, items));

var listItems = cut.WaitForComponents<ListItem>();

Assert.Equal(3, listItems.Count);
Assert.Equal("Item 1", listItems.ElementAt(0).Find(".list-item").TextContent);
Assert.Equal("Item 2", listItems.ElementAt(1).Find(".list-item").TextContent);

Waiting for a specific number of components

The WaitForComponents<TComponent>(int, TimeSpan?) overload waits until at least the specified number of component instances are rendered:

var items = new List<string> { "Item 1", "Item 2", "Item 3", "Item 4", "Item 5" };
var cut = Render<AsyncComponentLoader>(parameters => parameters
    .Add(p => p.Items, items));

var listItems = cut.WaitForComponents<ListItem>(5);

Assert.Equal(5, listItems.Count);
Note

This method waits for at least the specified number of components, not exactly that number. If more components are rendered than requested, the method will still succeed and return all found instances.

Note

These component wait methods use the same underlying mechanism as WaitForState() and will retry their checks every time the component under test renders.

If the timeout is reached, a WaitForFailedException exception is thrown with an appropriate error message indicating that the expected component(s) were not found within the timeout period.

Debugging code that uses WaitForState, WaitForAssertion, WaitForComponent, WaitForComponents, or WaitForElement

When bUnit detects that a debugger is attached (Debugger.IsAttached), it will automatically disable the timeout functionality of the "wait for" methods.

.NET Foundation

Supported by the .NET Foundation.