bUnit: Allow to test asynchronous exceptions during `OnAfterRenderAsync`

Is the feature request related to a problem? Please elaborate.

A component can fail during OnAfterRenderAsync. I might be wrong, but I see no way of testing that with the current API. Seems like nothing actually awaits the task that was returned. For example:

// FooComponent.razor
@inject IJSRuntime JSRuntime;
@code {
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        await JSRuntime.InvokeVoidAsync("foo");
        throw new InvalidOperationException();
    }
}
[Fact]
public async Task FooTest()
{
    using var ctx = new TestContext();
    var planned = ctx.JSInterop.SetupVoid("foo");

    var cut = ctx.RenderComponent<FooComponent>();
    planned.SetVoidResult(); // <-- After here the `OnAfterRenderAsync` progresses and throws an exception.
    
    planned.VerifyInvoke("foo");
}

I would like to be able to fail the test, because the OnAfterRenderAsync failed with an exception.

Note that this is not an issue with OnAfterRender or if the exception is thrown synchronously before any actual awaits. Then the test fails at the RenderComponent call, as it throws the exception synchronously.

The suggested solution

My immediate idea is that the rendered component should catch and store those unhandled exceptions somewhere in itself, with information on where they were thrown for. So an information containing “This exception was throw during this event handler during the nth rendering.” Then one could properly test for that.

Other idea: a method WaitForHandlersAsync that explicitly awaits the tasks returned from handlers invoked during rendering.

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Comments: 16 (7 by maintainers)

Most upvoted comments

Hi @V0ldek, depending on your use case, you can go with @linkdotnet suggestion.

However, the feature you are looking for is already there:

[Fact]
public async Task FooTest()
{
    using var ctx = new TestContext();
    var planned = ctx.JSInterop.SetupVoid("foo");

    var cut = ctx.RenderComponent<FooComponent>();
    planned.SetVoidResult(); // <-- After here the `OnAfterRenderAsync` progresses and throws an exception.
    
    planned.VerifyInvoke("foo");
    var expectedException = await ctx.Renderer.UnhandledException;
    // assert that expectedException is the correct type.

    // alternative, just get the exception directly
    var expectedException = Renderer.UnhandledException.Result;
}

Ps. Never use async void as your return type for test methods. While it is supported, its not recommended.