Image for post
Image for post

Unit test a Blazor component

Xavier Solau
Nov 18, 2020 · 7 min read

This Blazor article shows how to unit test a Blazor component. You can find the Github source repository this article series is based on here.

The Blazor article series

How to write the component to test?

In this article, I will present a way to test the component we have been writing in this previous article: Write a reusable Blazor component. I consider at this stage that we are now familiar with creating a reusable Blazor component and we will focus on how to unit test it.

Create the test project

In order to test our “Component1”, we need to set up a unit test project. Please note that I suppose that we are in the same solution as the component itself. In this article I will use xUnit as unit testing framework and Moq as mocking library but you can use whatever equivalent librairies you prefer.

Image for post
Image for post

Or using xUnit from the dotnet command line interface:

dotnet new xunit

To test the Blazor component I am also going to use a community project: bUnit. Since the project is still in beta, you will need to check the pre-release option in the Nuget Package Manager.

Image for post
Image for post

Tips: If you’re not using xUnit you can just reference the bunit.web nuget package.

We will also need to reference the component project to test in the test project csproj file : “MySharedComponents.Tests.csproj”

<ItemGroup>
<ProjectReference
Include="..\MySharedComponents\MySharedComponents.csproj" />
</ItemGroup>

Web testing strategy

To test our component, we need to verify that it is properly rendering at the HTML level, its behavior depending on parameters and interactions. In other words we are going to instantiate the component, make some interaction with it and check that it properly renders the HTML elements.

Thanks to bUnit, we can host/render the component and look up the HTML elements to check that it matches our behavioral expectations. But there is a preliminary task we need to do that consists in identifying our HTML element. When we write a unit testable component we have to think carefully and identify all key HTML elements.

For example in our “Component1” we are displaying a parent input parameter. To help unit testing of this behavior we can generate the HTML to make key elements easily queryable:

<div class="my-component">
This Blazor component is defined in the
<strong>MySharedComponents</strong> package and it is used from
<strong id="@ElementIds.ParentNameId">@ParentName</strong>.
...
</div>

with the ElementIds class defined this way:

public static class ElementIds
{
public const string ParentNameId = "parent-name";
}

In this razor code, it is easy to identify the HTML element that renders the ParentName property value. It is directly identified with the “id attribute of the HTML “strong element with the identification value “parent-name”.

Tips: Use C# const variables to help with your ids. This way it will be easier to avoid typing errors.

Let’s write our first rendering test

What do we want to test?

Now that we’ve got the test project setup, we can write our first test. Let’s start with something simple: We are just going to check that the value of the property ‘ParentName’ is properly displayed in the rendered HTML document.

Image for post
Image for post
Image for post
Image for post

Set up the test class

In order to test it, we need to create a test class “Component1UnitTests” where we will write the test “ItShouldDisplayTheParentName”.

How to render the component?

To render the component in our test bUnit provides us a “TestContext” class that will help to host the component and to render it.

Tips: it is disposable so don’t forget the using statement.

using var ctx = new TestContext();

The test context class defines a method “RenderComponent<ComponentType>” that will actually render the given component. In our case we just need to call the method with the “ParentName” parameter value that we want to display in the HTML.

// Render Component1 with the given ParentName property value.
var
cut = ctx.RenderComponent<Component1>(
ComponentParameterFactory.Parameter(
nameof(Component1.ParentName),
someValue));

Tips: Since the “ComponentParameterFactory” is a static class you can put it in the using directive to avoid the explicit prefix every time you need it:

// In the using area of your file.
using static
Bunit.ComponentParameterFactory;
// In you test method.
var renderedComponent = ctx.RenderComponent<Component1>(
Parameter(
nameof(Component1.ParentName),
someValue));

There is also another way to set up our test component with parameters using a builder syntax:

var renderedComponent = ctx.RenderComponent<Component1>(
builder =>
{
builder.Add(c => c.ParentName, someValue);
});

Let’s test things now!

Once your HTML is rendered we can check that it contains the right text value in the parent name location.

In our case, this is very easy since we have identified the html element that is supposed to contain the “ParentName” text value with the id “parent-name”.

Basically the RenderComponent returns a IRenderedComponent that provides methods to find HTML elements like this one with a CSS selector:

IElement Find(
IRenderedFragment renderedFragment,
string cssSelector)

It gives us in our test an easy way to assert that the value is properly rendered:

Assert.Equal(
someValue,
renderedComponent
.Find($"#{ElementIds.ParentNameId}")
.TextContent);

How about testing some behavior now?

So far we have seen how we can set up our test component and how to test the resulting HTML elements. Now we are going a bit further, we are going to make some interaction with the component to see if it behaves as expected.

What behavior?

The component is supposed to allow the user to click on a button in order to enter some text through Java script interop and to update a component property in addition of displaying the value in the HTML.

Image for post
Image for post

Set up the component with its dependencies

To test this behavior the component needs a IJSRuntime dependency to be available. Thanks to the bUnit TestContext, we can provide all services that the component requires. In our case we will provide a Mock of the IJSRuntime service:

using var ctx = new TestContext();// Mock JsRunTime that will return Test new value.
var jsrMock = new Mock<IJSRuntime>();
// Register the jsrMock as singleton.
ctx.Services.AddSingleton(jsrMock.Object);

This way the component will get the jsrMock in its injected properties. Since mocking class is not the purpose of this article, I won’t show how jsrMock is set up but you can find it in the test source code at the end of the article.

Tips: bUnit provides some useful helps to mock IJSRuntime (see documentation for more information).

Once the component is rendered with the right parameters, we can as usual look up the HTML elements:

// render the component
var renderedComponent = ctx.RenderComponent<Component1>(
Parameter(nameof(Component1.Text), someValue));
// Assert: first find the <text> element, then verify its initial content.
Assert.Equal(
someValue,
renderedComponent
.Find($"#{ElementIds.TextValueId}")
.TextContent);

How can we generate some events?

Now that our component is all set up and that the preliminary checks are done, we need to generate a user click event.

Once again bUnit is here to help. As we have seen before the IRenderedComponent gives us a way to find HTML elements to check contents but it also helps to generate events:

// Find and click the <button> element to set the
// text element value from the jsRuntime mock value.
await renderedComponent
.Find("button")
.ClickAsync(new MouseEventArgs());

The component is supposed to get an input from the user through the Javascript interop and display it in the HTML. There is nothing new here:

Assert.Equal(
newValue,
renderedComponent
.Find($"#{ElementIds.TextValueId}")
.TextContent);

It is also supposed to set the Text property of the component to the new input. Let’s check with the help of the IRenderedComponent instance that provides the Instance property:

// We also can inspect the component properties
// with the renderedComponent.Instance

Assert.Equal(newValue, renderedComponent.Instance.Text);

Did the component trigger its EventCallback?

Well, there is actually one last thing that could be interesting to test. Our component is supposed to trigger an EventCallback to allow the use of bidirectional binding. In our example:

// in the component class
[Parameter]
public string Text { get; set; }
[Parameter]
public EventCallback<string> TextChanged { get; set; }
// In the code using the component
<Component1
ParentName="Some text"
@bind-Text="_text">
</Component1>

bUnit provides a way to setup the test component with EventCallback parameters. So we can easily test that an event is actually triggered as expected:

// define a variable to store the event value.
string textChangedEventTriggered = null;
var renderedComponent = ctx.RenderComponent<Component1>(
EventCallback<string>(nameof(Component1.TextChanged),
(e) =>
{
textChangedEventTriggered = e;
}));
// Find and click the <button> element.
await renderedComponent
.Find("button")
.ClickAsync(new MouseEventArgs());
// Assert that the event has been triggered with the right value
Assert.Equal(newValue, textChangedEventTriggered);

Finally, here is the full code of our test class:

YounitedTech

Le blog Tech de Younited, où l’on parle de développement…

Thanks to Thomas Goldstein and Slim Ayache

Xavier Solau

Written by

YounitedTech

Le blog Tech de Younited, où l’on parle de développement, d’architecture, de microservices, de cloud, de data… Et de comment on s’organise pour faire tout ça. Ah, et on recrute aussi, on vous a dit ?

Xavier Solau

Written by

YounitedTech

Le blog Tech de Younited, où l’on parle de développement, d’architecture, de microservices, de cloud, de data… Et de comment on s’organise pour faire tout ça. Ah, et on recrute aussi, on vous a dit ?

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store