Triggering a Render Life Cycle on a Component
When a component under test is rendered, an instance of the IRenderedComponent<TComponent> type is returned. Through that, it is possible to cause the component under test to render again directly through the Render()
method or one of the SetParametersAndRender()
methods, or indirectly through the InvokeAsync()
method.
Warning
The Render()
and SetParametersAndRender()
methods are not available in the IRenderedFragment type that is returned when calling the non-generic version of GetComponentUnderTest()
in <Fixture>
-based Razor tests. Call the generic version of GetComponentUnderTest<TComponent>()
to get a IRenderedComponent<TComponent>.
Note
These methods are available and work the same way in both C#- and Razor-based tests. The examples below are from C#-based tests only.
Let's look at how to use each of these methods to cause a re-render.
Render
The Render()
method tells the renderer to re-render the component, i.e. go through its life-cycle methods (except for OnInitialized()
and OnInitializedAsync()
methods). To use it, do the following:
using var ctx = new TestContext();
var cut = ctx.RenderComponent<Heading>();
Assert.Equal(1, cut.RenderCount);
// Re-render without new parameters
cut.Render();
Assert.Equal(2, cut.RenderCount);
The highlighted line shows the call to Render()
.
Tip
The number of renders a component has been through can be inspected and verified using the RenderCount property.
SetParametersAndRender
The SetParametersAndRender(...)
methods tells the renderer to re-render the component with new parameters, i.e. go through its life-cycle methods (except for OnInitialized()
and OnInitializedAsync()
methods), passing the new parameters — but only the new parameters — to the SetParametersAsync()
method. To use it, do the following:
using var ctx = new TestContext();
var cut = ctx.RenderComponent<Item>(parameters => parameters
.Add(p => p.Value, "Foo")
);
cut.MarkupMatches("<span>Foo</span>");
// Re-render with new parameters
cut.SetParametersAndRender(parameters => parameters
.Add(p => p.Value, "Bar")
);
cut.MarkupMatches("<span>Bar</span>");
The highlighted line shows the call to SetParametersAndRender()
, which is also available as a version that takes the zero or more component parameters, e.g. created through the component parameter factory helper methods, if you prefer that method of passing parameters.
Note
Passing parameters to components through the SetParametersAndRender(...)
methods is identical to doing it with the RenderComponent<TComponent>(...)
methods, described in detail on the Passing Parameters to Components page.
InvokeAsync
Invoking methods on a component under test, which causes a render, e.g. by calling StateHasChanged
, can result in the following error, if the caller is running on another thread than the renderer's thread:
The current thread is not associated with the Dispatcher. Use InvokeAsync() to switch execution to the Dispatcher when triggering rendering or component state.
If you receive this error, you need to invoke your method inside an Action
delegate passed to the InvokeAsync(...)
method.
Let’s look at an example of this, using the <Calc>
component listed below:
<output>@result</output>
@code
{
int result = 0;
public void Calculate(int x, int y)
{
result = x + y;
StateHasChanged();
}
}
To invoke the Calculate()
method on the component instance, do the following:
using var ctx = new TestContext();
var cut = ctx.RenderComponent<Calc>();
// Indirectly re-renders through the call to StateHasChanged
// in the Calculate(x, y) method.
cut.InvokeAsync(() => cut.Instance.Calculate(1, 2));
cut.MarkupMatches("<output>3</output>");
The highlighted line shows the call to InvokeAsync(...)
, which is passed an Action
delegate that calls the Calculate
method.
Tip
The instance of a component under test is available through the Instance property.