Skip to content

when SetParametersAsync is used (only async) - BuildRenderTree is not invoked #1695

Description

Several issues are related to SetParametersAsync, but I am unsure whether they are the same as mine, so I tried creating a new incident.

Describe the bug
When a component has SetParametersAsync then BuildRenderTree is not invoked.

Example:
Testing this component:

using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Rendering;

namespace ClassLibrary1;

public class ComponentWithAsyncSetParameters : ComponentBase
{
	public override async Task SetParametersAsync(ParameterView parameters)
	{
		await Task.Delay(1000);
	}

	protected override void BuildRenderTree(RenderTreeBuilder builder)
	{
		builder.OpenElement(1, "h1");
		builder.AddContent(2, "HEADER TEXT");
		builder.CloseElement();
	}
}

public class ComponentWithoutAsyncSetParameters : ComponentBase
{
	public override Task SetParametersAsync(ParameterView parameters)
	{
		return Task.CompletedTask;
	}

	protected override void BuildRenderTree(RenderTreeBuilder builder)
	{
		builder.OpenElement(1, "h1");
		builder.AddContent(2, "HEADER TEXT");
		builder.CloseElement();
	}
}

public class ComponentWithoutSetParameters : ComponentBase
{
	protected override void BuildRenderTree(RenderTreeBuilder builder)
	{
		builder.OpenElement(1, "h1");
		builder.AddContent(2, "HEADER TEXT");
		builder.CloseElement();
	}
}

With this test:

using Bunit;
using ClassLibrary1;

namespace TestProject1;

public sealed class UnitTest1 : TestContext
{
	[Fact]
	public void TestComponentWithAsyncSetParameters()
	{
		const string expected = "<h1>HEADER TEXT</h1>";

		var component = RenderComponent<ComponentWithAsyncSetParameters>();
		component.MarkupMatches(expected);
	}
	
	[Fact]
	public void TestComponentWithoutAsyncSetParameters()
	{
		const string expected = "<h1>HEADER TEXT</h1>";

		var component = RenderComponent<ComponentWithoutAsyncSetParameters>();
		component.MarkupMatches(expected);
	}
	
	[Fact]
	public void TestComponentWithoutSetParameters()
	{
		const string expected = "<h1>HEADER TEXT</h1>";

		var component = RenderComponent<ComponentWithoutSetParameters>();
		component.MarkupMatches(expected);
	}
}

Results in this output:
Image
Image

Bunit.HtmlEqualException: HTML comparison failed.

Bunit.HtmlEqualException
HTML comparison failed. 

The following errors were found:
  1: The element at h1(0) is missing.

Actual HTML: 

Expected HTML: 
<h1>HEADER TEXT</h1>

   at Bunit.MarkupMatchesAssertExtensions.MarkupMatches(INodeList actual, INodeList expected, String userMessage) in /_/src/bunit.web/Asserting/MarkupMatchesAssertExtensions.cs:line 238
   at Bunit.MarkupMatchesAssertExtensions.MarkupMatches(IRenderedFragment actual, String expected, String userMessage) in /_/src/bunit.web/Asserting/MarkupMatchesAssertExtensions.cs:line 114
   at TestProject1.UnitTest1.TestComponentWithoutAsyncSetParameters() in C:\Users\Mihails Kuzmins\source\repos\ClassLibrary1\TestProject1\UnitTest1.cs:line 23
   at InvokeStub_UnitTest1.TestComponentWithoutAsyncSetParameters(Object, Object, IntPtr*)
   at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)

Expected behavior:
BuildRenderTree is invoked, and the valid HTML is rendered

Version info:

  • bUnit version: 1.38.5
  • .NET Runtime and Blazor version: net9.0; 9.0.3
  • OS type and version: Windows 10.0.26100.3624

Activity

  1. linkdotnet commented on Mar 31, 2025

    @linkdotnet
    Collaborator

    Hey @mku-vesca,

    maybe we can talk through examples of why certain things are happening. The easiest is ComponentWithAsyncSetParameters: You have a Task.Delay. Or a "real" async task that isn't directly finished.

    The RenderComponent doesn't await until all lifecycles are awaited as well. Blazor sees that you have a Task object and renders already something even if your SetParametersAsync method isn't completely done. bUnit behaves the same.

    While your render-tree might not need any parameter, Blazor anyways enters your SetParametersAsync method and batches the continuation (maybe it is that it doesn't have to wait 1 second). as bUnit is not blocking, you directly in your MarkupMatches method.

    The normal way to do it would be taking: WaitFor- Helpers: https://bunit.dev/docs/interaction/awaiting-async-state.html

    But even if you would do that, let's come to your second example:

    In your second example, you are overriding SetParametersAsync: https://source.dot.net/#Microsoft.AspNetCore.Components/ComponentBase.cs,259

    The default implementation has a bit logic, therefore base.SetParametersAsync is advised.

    If you are not calling base, no one is invoking a render cycle when the component is created the first time (there is a StateHasChanged call inside that chain).

    I would assume, that such a component will not be rendered in Blazor as well. Because bUnit doesn't do anything special here.

    So it boils down to: When overriding SetParametersAsync you have to manually instruct Blazor to render. In the last example, where you did not override, that is done automatically.

  2. linkdotnet commented on Mar 31, 2025

    @linkdotnet
    Collaborator

    That seems to be an internal issue of Blazor itself, which leads to an error.
    I expect that spinning up a "normal" Blazor WebApp will lead to the same exception.

    If it is fine with you, I will convert this into a discussion.

  3. locked and limited conversation to collaborators on Mar 31, 2025
  4. converted this issue into a discussion #1696 on Mar 31, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions