Skip to content

Commit 356bb9a

Browse files
committed
Docs: Finished awaiting-async-state
1 parent 51c017b commit 356bb9a

File tree

3 files changed

+103
-1
lines changed

3 files changed

+103
-1
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<p>@text</p>
2+
3+
@code
4+
{
5+
string text = string.Empty;
6+
[Parameter] public Task<string> TextService { get; set; }
7+
8+
protected override async Task OnInitializedAsync()
9+
{
10+
text = await TextService;
11+
}
12+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
using Microsoft.AspNetCore.Components.Web;
2+
using Xunit;
3+
using Bunit;
4+
using System;
5+
using System.Threading.Tasks;
6+
7+
namespace Bunit.Docs.Samples
8+
{
9+
public class AsyncDataTest
10+
{
11+
[Fact]
12+
public void LoadDataAsync()
13+
{
14+
// Arrange
15+
using var ctx = new TestContext();
16+
var textService = new TaskCompletionSource<string>();
17+
var cut = ctx.RenderComponent<AsyncData>(parameters => parameters
18+
.Add(p => p.TextService, textService.Task)
19+
);
20+
21+
// Act - set the awaited result from the text service
22+
textService.SetResult("Hello World");
23+
24+
// Wait for state before continuing test
25+
cut.WaitForState(() => cut.Find("p").TextContent == "Hello World");
26+
27+
// Assert - verify result has been set
28+
cut.MarkupMatches("<p>Hello World</p>");
29+
}
30+
31+
[Fact]
32+
public void LoadDataAsyncWithTimeout()
33+
{
34+
// Arrange
35+
using var ctx = new TestContext();
36+
var textService = new TaskCompletionSource<string>();
37+
var cut = ctx.RenderComponent<AsyncData>(parameters => parameters
38+
.Add(p => p.TextService, textService.Task)
39+
);
40+
41+
// Act - set the awaited result from the text service
42+
textService.SetResult("Long time");
43+
44+
// Wait for state before continuing test
45+
cut.WaitForState(() => cut.Find("p").TextContent == "Long time", TimeSpan.FromSeconds(2));
46+
47+
// Assert - verify result has been set
48+
cut.MarkupMatches("<p>Long time</p>");
49+
}
50+
51+
}
52+
}

docs/site/docs/interaction/awaiting-async-state.md

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,42 @@ uid: awaiting-async-state
33
title: Awaiting an Asynchronous State Change in a Component Under Test
44
---
55

6-
# Awaiting an Asynchronous State Change
6+
# Awaiting an Asynchronous State Change
7+
8+
A test can fail if a component performs asynchronous renders, e.g. because it was awaiting an task to complete before continuing its render life-cycle. For example, if a component is waiting for a async web service to return data to it in the `OnInitializedAsync()` life-cycle method, before rendering it to the render tree.
9+
10+
This happens because tests execute in the test framework's synchronization context and the test renderer executes renders in its own synchronization context.
11+
12+
bUnit comes with two methods that helps deal with this issue, the [`WaitForState(Func<Boolean>, TimeSpan?)`](xref:Bunit.RenderedFragmentWaitForHelperExtensions.WaitForState(Bunit.IRenderedFragmentBase,System.Func{System.Boolean},System.Nullable{System.TimeSpan})) method covered on this page, and the [`WaitForAssertion(Action, TimeSpan?)`](xref:Bunit.RenderedFragmentWaitForHelperExtensions.WaitForAssertion(Bunit.IRenderedFragmentBase,System.Action,System.Nullable{System.TimeSpan})) method covered on the <xref:async-assertion> page.
13+
14+
## Waiting for state using `WaitForState`
15+
16+
The [`WaitForState(Func<Boolean>, TimeSpan?)`](xref:Bunit.RenderedFragmentWaitForHelperExtensions.WaitForState(Bunit.IRenderedFragmentBase,System.Func{System.Boolean},System.Nullable{System.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).
17+
18+
Let us look at an example. Consider the following `<AsyncData>` component, who 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.
19+
20+
[!code-html[AsyncData.razor](../../../samples/components/AsyncData.razor)]
21+
22+
To test the `<AsyncData>` component, do the following:
23+
24+
[!code-csharp[AsyncDataTest.cs](../../../samples/tests/xunit/AsyncDataTest.cs?start=15&end=28&highlight=2,8,11,14)]
25+
26+
This is what happens in the test:
27+
28+
1. The test uses a `TaskCompletionSource<string>` to simulate an async web service.
29+
2. In the second highlighted line, the result is provided to the component through the `textService`. This causes the component to re-render.
30+
3. In the third highlighted line, the `WaitForState()` method is used to block the test until the predicate provided to it returns true.
31+
4. Finally, the tests assertion step can execute, knowing that the desired state has been reached.
32+
33+
> [!NOTE]
34+
> The wait predicate and an assertion should not verify the same thing. Instead, use the [`WaitForAssertion(...)`](xref:Bunit.RenderedFragmentWaitForHelperExtensions.WaitForAssertion(Bunit.IRenderedFragmentBase,System.Action,System.Nullable{System.TimeSpan})) method covered on the <xref:async-assertion> page instead.
35+
36+
### Controlling wait timeout
37+
38+
The timeout, which defaults to one second, can be controlled by passing a `TimeSpan` as the second argument to the `WaitForState()` method, e.g.:
39+
40+
[!code-csharp[](../../../samples/tests/xunit/AsyncDataTest.cs?start=45&end=45)]
41+
42+
If the timeout is reached, a <xref:Bunit.Extensions.WaitForHelpers.WaitForFailedException> exception is thrown with the following error message:
43+
44+
> The state predicate did not pass before the timeout period passed.

0 commit comments

Comments
 (0)