Passing parameters to components
bUnit comes with a number of ways to pass parameters to components under test:
In tests written in
.razor
files, passing parameters is most easily done with inside an inline Razor template passed to theRender
method, although the parameter passing option available in tests written in C# files is also available here.In tests written in
.cs
files, bUnit includes a strongly typed builder. There are two methods in bUnit that allow passing parameters in C#-based test code:RenderComponent
method on the test context, which is used to render a component initially.SetParametersAndRender
method on a rendered component, which is used to pass new parameters to an already rendered component.
In the following sub sections, we will show both .cs
- and .razor
-based test code; just click between them using the tabs.
Note
The examples below are written using xUnit, but the code is the same with NUnit and MSTest.
The example tests are inheriting from the TestContext
as described in the Writing tests for Blazor components page. If your test class is not inheriting from the TestContext
, then you should explicitly instantiate the TestContext
in your tests. The recommended way is to inherit from the TestContext
. An example below will demonstrate how to instantiate a TestContext
per test method.
Regular parameters
A regular parameter is one that is declared using the [Parameter]
attribute. The following subsections will cover both non-Blazor type parameters, e.g. int
and List<string>
, and the special Blazor types like EventCallback
and RenderFragment
.
Non-Blazor type parameters
Let's look at an example of passing parameters that takes types which are not special to Blazor, i.e.:
public class NonBlazorTypesParams : ComponentBase
{
[Parameter]
public int Numbers { get; set; }
[Parameter]
public List<string> Lines { get; set; }
}
This can be done like this:
public class NonBlazorTypesParamsTest : TestContext
{
[Fact]
public void Test()
{
var lines = new List<string> { "Hello", "World" };
var cut = RenderComponent<NonBlazorTypesParams>(parameters => parameters
.Add(p => p.Numbers, 42)
.Add(p => p.Lines, lines)
);
}
}
The example uses the ComponentParameterCollectionBuilder<TComponent>'s Add
method, which takes a parameter selector expression that selects the parameter using a lambda, and forces you to provide the correct type for the value. This makes the builder's methods strongly typed and refactor-safe.
EventCallback parameters
This example will pass parameters to the following two EventCallback
parameters:
public class EventCallbackParams : ComponentBase
{
[Parameter]
public EventCallback<MouseEventArgs> OnClick { get; set; }
[Parameter]
public EventCallback OnSomething { get; set; }
}
This can be done like this:
public class EventCallbackParamsTest : TestContext
{
[Fact]
public void Test()
{
Action<MouseEventArgs> onClickHandler = _ => { };
Action onSomethingHandler = () => { };
var cut = RenderComponent<EventCallbackParams>(parameters => parameters
.Add(p => p.OnClick, onClickHandler)
.Add(p => p.OnSomething, onSomethingHandler)
);
}
}
The example uses the ComponentParameterCollectionBuilder<TComponent>'s Add
method, which takes a parameter selector expression that selects the parameter using a lambda, and forces you to provide the correct type of callback method. This makes the builder's methods strongly typed and refactor-safe.
ChildContent parameters
The ChildContent
parameter in Blazor is represented by a RenderFragment
. In Blazor, this can be regular HTML markup, it can be Razor markup, e.g. other component declarations, or a mix of the two. If it is another component, then that component can also receive child content, and so forth.
The following subsections have different examples of child content being passed to the following component:
public class ChildContentParams : ComponentBase
{
[Parameter]
public RenderFragment ChildContent { get; set; }
}
Passing HTML to the ChildContent parameter
public class ChildContentParams1Test : TestContext
{
[Fact]
public void Test()
{
var cut = RenderComponent<ChildContentParams>(parameters => parameters
.AddChildContent("<h1>Hello World</h1>")
);
}
}
The example uses the ComponentParameterCollectionBuilder<TComponent>'s AddChildContent
method to pass an HTML markup string as the input to the ChildContent
parameter.
Passing a component without parameters to the ChildContent parameter
To pass a component, e.g. the classic <Counter>
component, which does not take any parameters itself, to a ChildContent
parameter, do the following:
public class ChildContentParams2Test : TestContext
{
[Fact]
public void Test()
{
var cut = RenderComponent<ChildContentParams>(parameters => parameters
.AddChildContent<Counter>()
);
}
}
The example uses the ComponentParameterCollectionBuilder<TComponent>'s AddChildContent<TChildComponent>
method, where TChildComponent
is the (child) component that should be passed to the component under test's ChildContent
parameter.
Passing a component with parameters to the ChildContent parameter
To pass a component with parameters to a component under test, e.g. the <Alert>
component with the following parameters, do the following:
[Parameter] public string Heading { get; set; }
[Parameter] public AlertType Type { get; set; }
[Parameter] public RenderFragment ChildContent { get; set; }
public class ChildContentParams3Test : TestContext
{
[Fact]
public void Test()
{
var cut = RenderComponent<ChildContentParams>(parameters => parameters
.AddChildContent<Alert>(alertParameters => alertParameters
.Add(p => p.Heading, "Alert heading")
.Add(p => p.Type, AlertType.Warning)
.AddChildContent("<p>Hello World</p>")
)
);
}
}
The example uses the ComponentParameterCollectionBuilder<TComponent>'s AddChildContent<TChildComponent>
method, where TChildComponent
is the (child) component that should be passed to the component under test. The AddChildContent<TChildComponent>
method takes an optional ComponentParameterCollectionBuilder<TComponent> as input, which can be used to pass parameters to the TChildComponent
component, which in this case is the <Alert>
component.
Passing a mix of Razor and HTML to a ChildContent parameter
Some times you need to pass multiple different types of content to a ChildContent parameter, e.g. both some markup and a component. This can be done in the following way:
public class ChildContentParams4Test : TestContext
{
[Fact]
public void Test()
{
var cut = RenderComponent<ChildContentParams>(parameters => parameters
.AddChildContent("<h1>Below you will find a most interesting alert!</h1>")
.AddChildContent<Alert>(childParams => childParams
.Add(p => p.Heading, "Alert heading")
.Add(p => p.Type, AlertType.Warning)
.AddChildContent("<p>Hello World</p>")
)
);
}
}
Passing a mix of markup and components to a ChildContent
parameter is done by simply calling the ComponentParameterCollectionBuilder<TComponent>'s AddChildContent()
methods as seen here.
RenderFragment parameters
A RenderFragment
parameter is very similar to the special ChildContent
parameter described in the previous section, since a ChildContent
parameter is of type RenderFragment
. The only difference is the name, which must be anything other than ChildContent
.
In Blazor, a RenderFragment
parameter can be regular HTML markup, it can be Razor markup, e.g. other component declarations, or it can be a mix of the two. If it is another component, then that component can also receive child content, and so forth.
The following subsections have different examples of content being passed to the following component's RenderFragment
parameter:
public class RenderFragmentParams : ComponentBase
{
[Parameter]
public RenderFragment Content { get; set; }
}
Passing HTML to a RenderFragment parameter
public class RenderFragmentParams1Test : TestContext
{
[Fact]
public void Test()
{
var cut = RenderComponent<RenderFragmentParams>(parameters => parameters
.Add(p => p.Content, "<h1>Hello World</h1>")
);
}
}
The example uses the ComponentParameterCollectionBuilder<TComponent>'s Add
method to pass an HTML markup string as the input to the RenderFragment
parameter.
Passing a component without parameters to a RenderFragment parameter
To pass a component such as the classic <Counter>
component, which does not take any parameters, to a RenderFragment
parameter, do the following:
public class RenderFragmentParams2Test : TestContext
{
[Fact]
public void Test()
{
var cut = RenderComponent<RenderFragmentParams>(parameters => parameters
.Add<Counter>(p => p.Content)
);
}
}
The example uses the ComponentParameterCollectionBuilder<TComponent>'s Add<TChildComponent>
method, where TChildComponent
is the (child) component that should be passed to the RenderFragment
parameter.
Passing a component with parameters to a RenderFragment parameter
To pass a component with parameters to a RenderFragment
parameter, e.g. the <Alert>
component with the following parameters, do the following:
[Parameter] public string Heading { get; set; }
[Parameter] public AlertType Type { get; set; }
[Parameter] public RenderFragment ChildContent { get; set; }
public class RenderFragmentParams3Test : TestContext
{
[Fact]
public void Test()
{
var cut = RenderComponent<RenderFragmentParams>(parameters => parameters
.Add<Alert>(p => p.Content, alertParameters => alertParameters
.Add(p => p.Heading, "Alert heading")
.Add(p => p.Type, AlertType.Warning)
.AddChildContent("<p>Hello World</p>")
)
);
}
}
The example uses the ComponentParameterCollectionBuilder<TComponent>'s Add<TChildComponent>
method, where TChildComponent
is the (child) component that should be passed to the RenderFragment
parameter. The Add<TChildComponent>
method takes an optional ComponentParameterCollectionBuilder<TComponent> as input, which can be used to pass parameters to the TChildComponent
component, which in this case is the <Alert>
component.
Passing a mix of Razor and HTML to a RenderFragment parameter
Some times you need to pass multiple different types of content to a RenderFragment
parameter, e.g. both markup and and a component. This can be done in the following way:
public class RenderFragmentParams4Test : TestContext
{
[Fact]
public void Test()
{
var cut = RenderComponent<RenderFragmentParams>(parameters => parameters
.Add(p => p.Content, "<h1>Below you will find a most interesting alert!</h1>")
.Add<Alert>(p => p.Content, childParams => childParams
.Add(p => p.Heading, "Alert heading")
.Add(p => p.Type, AlertType.Warning)
.AddChildContent("<p>Hello World</p>")
)
);
}
}
Passing a mix of markup and components to a RenderFragment
parameter is simply done by calling the ComponentParameterCollectionBuilder<TComponent>'s Add()
methods or using the ChildContent()
factory methods in ComponentParameterFactory, as seen here.
Templates parameters
Template parameters are closely related to the RenderFragment
parameters described in the previous section. The difference is that a template parameter is of type RenderFragment<TValue>
. As with a regular RenderFragment
, a RenderFragment<TValue>
template parameter can consist of regular HTML markup, it can be Razor markup, e.g. other component declarations, or it can be a mix of the two. If it is another component, then that component can also receive child content, and so forth.
The following examples renders a template component which has a RenderFragment<TValue>
template parameter:
@typeparam TItem
<div id="generic-list">
@foreach (var item in Items)
{
@Template(item)
}
</div>
@code
{
[Parameter]
public IEnumerable<TItem> Items { get; set; }
[Parameter]
public RenderFragment<TItem> Template { get; set; }
}
Passing HTML-based templates
To pass a template into a RenderFragment<TValue>
parameter that just consists of regular HTML markup, do the following:
public class TemplateParams1Test : TestContext
{
[Fact]
public void Test()
{
var cut = RenderComponent<TemplateParams<string>>(parameters => parameters
.Add(p => p.Items, new[] { "Foo", "Bar", "Baz" })
.Add(p => p.Template, item => $"<span>{item}</span>")
);
}
}
The examples pass a HTML markup template into the component under test. This is done with the help of a Func<TValue, string>
delegate which takes whatever the template value is as input, and returns a (markup) string. The delegate is automatically turned into a RenderFragment<TValue>
type and passed to the template parameter.
The example uses the ComponentParameterCollectionBuilder<TComponent>'s Add
method to first add the data to the Items
parameter and then to a Func<TValue, string>
delegate.
The delegate creates a simple markup string in the example.
Passing a component-based template
To pass a template into a RenderFragment<TValue>
parameter, which is based on a component that receives the template value as input (in this case, the <Item>
component listed below), do the following:
<span>@Value</span>
@code
{
[Parameter]
public string Value { get; set; }
}
public class TemplateParams2Test : TestContext
{
[Fact]
public void Test()
{
var cut = RenderComponent<TemplateParams<string>>(parameters => parameters
.Add(p => p.Items, new[] { "Foo", "Bar", "Baz" })
.Add<Item, string>(p => p.Template, value => itemParams => itemParams
.Add(p => p.Value, value)
)
);
}
}
The example creates a template with the <Item>
component listed above.
Unmatched parameters
An unmatched parameter is a parameter that is passed to a component under test, and which does not have an explicit [Parameter]
parameter but instead is captured by a [Parameter(CaptureUnmatchedValues = true)]
parameter.
In the follow examples, we will pass an unmatched parameter to the following component:
public class UnmatchedParams : ComponentBase
{
[Parameter(CaptureUnmatchedValues = true)]
public Dictionary<string, object> InputAttributes { get; set; }
}
public class UnmatchedParamsTest : TestContext
{
[Fact]
public void Test()
{
var cut = RenderComponent<UnmatchedParams>(parameters => parameters
.AddUnmatched("some-unknown-param", "a value")
);
}
}
The examples passes in the parameter some-unknown-param
with the value a value
to the component under test.
Cascading Parameters and Cascading Values
Cascading parameters are properties with the [CascadingParameter]
attribute. There are two variants: named and unnamed cascading parameters. In Blazor, the <CascadingValue>
component is used to provide values to cascading parameters, which we also do in tests written in .razor
files. However, for tests written in .cs
files we need to do it a little differently.
The following examples will pass cascading values to the <CascadingParams>
component listed below:
@code
{
[CascadingParameter]
public bool IsDarkTheme { get; set; }
[CascadingParameter(Name = "LoggedInUser")]
public string UserName { get; set; }
[CascadingParameter(Name = "LoggedInEmail")]
public string Email { get; set; }
}
Passing unnamed cascading values
To pass the unnamed IsDarkTheme
cascading parameter to the <CascadingParams>
component, do the following:
public class CascadingParams1Test : TestContext
{
[Fact]
public void Test()
{
var isDarkTheme = true;
var cut = RenderComponent<CascadingParams>(parameters => parameters
.Add(p => p.IsDarkTheme, isDarkTheme)
);
}
}
The example pass the variable isDarkTheme
to the cascading parameter IsDarkTheme
using the Add
method on the ComponentParameterCollectionBuilder<TComponent> with the parameter selector to explicitly select the desired cascading parameter and pass the unnamed parameter value that way.
Passing named cascading values
To pass a named cascading parameter to the <CascadingParams>
component, do the following:
public class CascadingParams2Test : TestContext
{
[Fact]
public void Test()
{
var cut = RenderComponent<CascadingParams>(parameters => parameters
.Add(p => p.UserName, "Name of User")
);
}
}
The example pass in the value Name of User
to the cascading parameter with the name LoggedInUser
. Note that the name of the parameter is not the same as the property of the parameter, e.g. LoggedInUser
vs. UserName
. The example uses the Add
method on the ComponentParameterCollectionBuilder<TComponent> with the parameter selector to select the cascading parameter property and pass the parameter value that way.
Passing multiple, named and unnamed, cascading values
To pass all cascading parameters to the <CascadingParams>
component, do the following:
public class CascadingParams3Test : TestContext
{
[Fact]
public void Test()
{
var isDarkTheme = true;
var cut = RenderComponent<CascadingParams>(parameters => parameters
.Add(p => p.IsDarkTheme, isDarkTheme)
.Add(p => p.UserName, "Name of User")
.Add(p => p.Email, "[email protected]")
);
}
}
The example passes both the unnamed IsDarkTheme
cascading parameter and the two named cascading parameters (LoggedInUser
, LoggedInEmail
). It does this using the Add
method on the ComponentParameterCollectionBuilder<TComponent> with the parameter selector to select both the named and unnamed cascading parameters and pass values to them that way.
Rendering a component under test inside other components
It is possible to nest a component under tests inside other components, if that is required to test it. For example, to nest the <HelloWorld>
component inside the <Wrapper>
component do the following:
{
[Fact]
public void Test()
{
var wrapper = RenderComponent<Wrapper>(parameters => parameters
.AddChildContent<HelloWorld>()
);
var cut = wrapper.FindComponent<HelloWorld>();
}
}
The example renders the <HelloWorld>
component inside the <Wrapper>
component. What is special in both cases is the use of the FindComponent<HelloWorld>()
that returns a IRenderedComponent<HelloWorld>
. This is needed because the RenderComponent<Wrapper>
method call returns an IRenderedComponent<Wrapper>
instance, that provides access to the instance of the <Wrapper>
component, but not the <HelloWorld>
-component instance.
Configure two-way with component parameters (@bind
directive)
To set up two-way binding to a pair of component parameters on a component under test, e.g. the Value
and ValueChanged
parameter pair on the component below, do the following:
@code {
[Parameter] public string Value { get; set; } = string.Empty;
[Parameter] public EventCallback<string> ValueChanged { get; set; }
}
public class TwoWayBindingTest : TestContext
{
[Fact]
public void Test()
{
var currentValue = string.Empty;
RenderComponent<TwoWayBinding>(parameters =>
parameters.Bind(
p => p.Value,
currentValue,
newValue => currentValue = newValue));
}
}
The example uses the Bind
method to setup two-way binding between the Value
parameter and ValueChanged
parameter, and the local variable in the test method (currentValue
). The Bind
method is a shorthand for calling the the Add
method for the Value
parameter and ValueChanged
parameter individually.
TestContext
per test method
There are scenarios where it is not possible or not desirable to inherit from TestContext
in the test class. In those cases, it is possible to create a new TestContext
instance per test method. As the TestContext
class implements IDisposable
, it is recommended to use the using
statement to ensure that the instance is disposed of after the test method has run.
public class HelloWorldExplicitContext
{
[Fact]
public void HelloWorldComponentRendersCorrectly()
{
// Arrange
using var ctx = new TestContext();
// Act
var cut = ctx.RenderComponent<HelloWorld>();
// Assert
cut.MarkupMatches("<h1>Hello world from Blazor</h1>");
}
}
Getting an InvalidOperationException
When the razor syntax is used and the test throws the following exception:
System.InvalidOperationException: The render handle is not yet assigned.
This usually means that the test class (Blazor component where the tests is declared in) is direclty inheriting from ComponentBase
, as is the default for all Blazor components.
The solution is to inherit from bUnits TestContext
instead, i.e.:
@inherits TestContext
@code {
[Fact]
public void Test1()
{
// test code
}
}
Limitations of rendering a RenderFragment
inside a test
When rendering a RenderFragment
using the Render(RenderFragment) method, the created IRenderedFragment is static. This means that it will not re-render even if events are triggered.
@inherits TestContext
@code {
[Fact]
public void Button_clicked_string_gets_updated()
{
var output = string.Empty;
var cut = Render(@<button @onclick='@(() => output = "Success")'>@output</button>);
cut.Find("button").Click();
// This will pass, as events are triggered and the function get executed
output.ShouldBe("Success");
// This will fail, as the markup will not get updated
cut.Find("button").TextContent.ShouldBe("Success");
}
}
Passing query parameters (SupplyParameterFromQuery
) to a component
In .NET 6 and later, components can receive parameters from a query string if the parameter is annotated with the [SupplyParameterFromQuery]
attribute in addition to the [Parameter]
attribute.
In .NET 8 however, the [Parameter]
attribute is no longer required, which means a value cannot be passed to the component during testing using the normal methods, e.g. the ComponentParameterCollectionBuilder<TComponent>'s Add
method, if a component parameter is only annotated with the [SupplyParameterFromQuery]
attribute. Instead, pass a query string parameters by setting it using the FakeNavigationManager.
For example:
@code {
[SupplyParameterFromQuery]
public string Name { get; set; }
}
A simple example of how to test a component that receives parameters from the query string:
@inherits TestContext
@code {
[Fact]
public void Component_receives_parameters_from_query_string()
{
var navigationManager = Services.GetRequiredService<NavigationManager>();
var uri = navigationManager.GetUriWithQueryParameter("Name", "bUnit");
navigationManager.NavigateTo(uri);
var cut = RenderComponent<SupplyFromQueryParameterComponent>();
cut.Instance.Name.ShouldBe("bUnit");
}
}