What is unit testing?
Unit testing is a level of software testing that is used by developers to ensure that the implemented logic meets its requirements and behaves as expected.
What is bUnit?
bUnit is a testing library for Blazor components used to write comprehensive and stable unit test cases. It allows you to write test cases in C# or Razor syntax to verify outcomes with semantic HTML diffing and comparison logic. With it, you can interact with components, event handlers, and cascading values; inject services; mock IJSRuntime; and take snapshots.
bUnit is not a unit test runner, but it is built on top of traditional unit testing frameworks such as xUnit, NUnit, and MSTest.
Now, let’s learn how to write a test in C# for Blazor components!
Topics we’ll cover
- Create a simple Blazor application.
- Create a new bUnit test project.
- Create a unit test case.
- Create a test case for event handlers.
- Pass parameters to the component.
- Configure bUnit into a setup.
- Inject services.
- Mock IJSRuntime.
- Integrate bUnit with an Azure Pipeline.
- Advantages of bUnit.
- Limitations of bUnit.
Prerequisites
- Visual Studio 2019.
- ASP.NET and web development workload.
Create a simple Blazor application
Follow these steps to create a Blazor application:
- Open Visual Studio 2019.
- Click Create a new project.
- Select Blazor App and then click Next as shown in the following screenshot.
- Provide a Project name or accept the default project name. Then, select a Location for the project and click Create.
The Blazor app will be created.
Creating a new bUnit test project
Follow these steps to create a project for testing Blazor components:
- Create a new NUnit testing project. Refer to this guide if you don’t know how: https://www.syncfusion.com/blogs/post/how-to-integrate-unit-testing-with-asp-net-core-3-1.aspx.
- Right-click the test project and select Manage Nuget Packages as shown in the following screenshot.
- Search for the keyword bunit.web. The results will be displayed as follows. Select bunit.web and install it.
- Ensure that the NuGet package is installed.
Create a unit test case
Now, we are going to write a test case for a Razor component.
- Add a Razor component to the Blazor project as shown in the following screenshot.
- Add a test class file in the test project.
- Write the following code into the test class.
[SetUp] public void Setup() { } [Test] public void HelloSyncfusionComponentTest() { // Arrange using var context = new Bunit.TestContext(); string expectedMarkup = "<h3>HelloSyncfusion</h3>"; // Act var cut = context.RenderComponent<BlazorbUnit.HelloSyncfusion>(); // Assert cut.MarkupMatches(expectedMarkup); }
In this code:
using var context = new Bunit.TestContext(); creates an instance of the bunit.TestContext class. Here, we are using the using var syntax to avoid unnecessary indention.
string expectedHTML = “<h3>HelloSyncfusion</h3>”; is Blazor component markup.
var cut = context.RenderComponent<BlazorbUnit.HelloSyncfusion>(); renders the Blazor component using TestContext.
cut.MarkupMatches(expectedHTML); verifies the rendered markup from the component using the MarkupMatches method. This performs a semantic comparison of the expected markup with the rendered markup.
- Build the project and ensure the build succeeds. Then select View > Test Explorer as in the following screenshot.
Now, you will see the list of test methods.
- Right-click the HelloSyncfusionComponentTest method, and then click Run.
The test run is successful, and the test case passes. You can also alter the test case if it fails.
Note: This is a component rendered test case. So, if the test case passes, the component will be rendered successfully.
Test case for event handlers
Follow this procedure to create a test case for an event handler:
- Use the code as shown below in the HelloSyncfusion.razor component.
@page "/HelloSyncfusion" <h3>HelloSyncfusion</h3> <button class="btn btn-primary" @onclick="ClickMeToSayHi">Ping Me</button> @if (IsVisible) { <h5>Hi Syncfusion, nice to meet you :)</h5> } @code { public bool IsVisible { get; set; } = false; protected void ClickMeToSayHi() { IsVisible = true; } }
In the above code, we have written a test case for the @onclick=”ClickMeToSayHi” event. After the button is clicked, a message will be shown below it. This operation is handled in this event.
- Use the following code in HelloSyncfusionTest.cs.
[Test] public void SayHiByClick() { // Arrange using var context = new Bunit.TestContext(); // Act var cut = Context.RenderComponent<BlazorbUnit.HelloSyncfusion>(); cut.Find("button").Click(); // Assert cut.Find("h5").MarkupMatches("<h5>Hi Syncfusion, Nice to meet you :)</h5>"); }
From the test method, var cut = Context.RenderComponent<BlazorbUnit.HelloSyncfusion>(); renders the Blazor component.
Then, using the Find method, the button is located using the component tag, class, or ID, and the click event is triggered by using the codecut.Find(“button”)[0].Click();
This triggers the click event and shows the message. Then, find the message markup and compare it with the expected markup using following code.
cut.Find("h5").MarkupMatches("<h5>Hi Syncfusion, Nice to meet you :)</h5>");
Note: Before triggering the click event, the message is hidden. So, we cannot get the message’s markup.
- Open the Test Explorer and run the test.
The Click event test case has passed.
Passing parameters to the component
bUnit has plenty of ways to pass parameters. Here, I am going to explain non-Blazor type parameters.
- Before passing parameters, you have to import Bunit.ComponentParameterFactory namespace.
using static Bunit.ComponentParameterFactory;
- Add a parameter and cascading value property as shown below:
<button class="btn btn-primary" @onclick="ClickToShowParameters">Show Parameters</button> @if (ShowParameters) { <div class="parameter">Parameter: @Parameter</div> <div class="cascadingParameter">Cascading Parameter: @CascadingParameter</div> } @code { public bool ShowParameters { get; set; } = false; [Parameter] public int Parameter { get; set; } [CascadingParameter] public int CascadingParameter { get; set; } protected void ClickToShowParameters() { ShowParameters = true; } }
In the above code, we have set the property for the parameter and cascading parameter.
[Parameter] public int Parameter { get; set; } [CascadingParameter] public int CascadingParameter { get; set; }
Both properties are going to catch the parameter and cascading parameter when it is passed.The parameter and cascading parameter values are shown by clicking button. The @onclick=”ClickToShowParameters” event is going to perform this operation.
- Add code in the test method as shown below.
[Test] public void PassingParameters() { // Arrange using var context = new Bunit.TestContext(); int parameter = 1; int cascadingParameter = 2; // Act var cut = Context.RenderComponent<BlazorbUnit.HelloSyncfusion>(("Parameter", parameter), CascadingValue(cascadingParameter)); cut.FindAll("button")[1].Click(); // Assert cut.Find(".parameter").MarkupMatches("<div class='parameter'>Parameter: 1</div>"); cut.Find(".cascadingParameter").MarkupMatches("<div class='cascadingParameter'>Cascading Parameter: 2</td>"); }
In this code,
var cut = context.RenderComponent<BlazorbUnit.HelloSyncfusion>((“Parameter”, parameter), CascadingValue(cascadingParameter)); is the way to pass a parameter. Here, “Parameter” is the property name of the parameter initialized in the Blazor component.Find the button using the FindAll method and trigger the click event cut.FindAll(“button”)[1].Click();. Passed values will be shown in the Razor component, and then we can match the markup using following code.
cut.Find(".parameter").MarkupMatches("<div class='parameter'>Parameter: 1</div>"); cut.Find(".cascadingParameter").MarkupMatches("<div class='cascadingParameter'>Cascading Parameter: 2</td>");
- Open the Test Explorer and run the test case.
Setup configuration
Here, we are going to configure a bUnit instance into a setup method. Once configured into the setup, you don’t need to create a new instance in every test method. Instead, you can use the common instance. Then add Context.Dispose(); code into TearDown method to dispose context. Context will be disposed once each test run.
Here, bUnit TestContext instance code was removed in each test method and instantiated in the setup method.
Injecting services
Injecting a service is essential if you are using a service in your Blazor component. Let me explain how to inject services in a test method:
- Consider a service named SyncfusionService in a Blazor project. We will then inject the service into the component like in the following screenshots.
- Then, we must inject the service in the test method too, like the following screenshot.Here is the code to mock the service.
Context.Services.AddSingleton<BlazorbUnit.SyncfusionService>(new BlazorbUnit.SyncfusionService());
Mocking IJSRuntime
A mock object allows copying or cloning the behavior of a class and interface. This is used when interacting with test cases.
Follow these steps to mock IJSRuntime:
- Mocking IJSRuntime is essential If you are injecting IJSRuntime in a Blazor component. Consider a scenario where you have used IJSRuntime in the HelloSyncfusion.razor component.
- Add the namespaces, using Microsoft.Extensions.DependencyInjection and using Bunit.TestDoubles.JSInterop before mocking IJSRuntime in the test class.
- Add Context.Services.AddMockJsRuntime(); into the setup method to mock IJSRuntime.
We can also mock other objects as needed.
How to integrate with an Azure Pipeline
bUnit is built on a testing framework. So, there is no need to do separate configuration in an Azure Pipeline. If you have configured NUnit in an Azure pipeline, that is more than enough to put bUnit test cases into it.
Refer to this blog to learn how to configure an Azure Pipeline:
https://www.syncfusion.com/blogs/post/easy-steps-to-integrate-azure-pipelines.aspx
bUnit Advantages
- Builds on top of traditional unit testing frameworks, so it doesn’t disturb existing unit test cases.
- Easy to integrate with an existing test project.
- Makes it easy to build a stable Blazor test bed.
- Provides more flexible ways to write test cases.
bUnit Limitations
- Razor-based testing is not stable.
- Only a beta version of bUnit is currently available.
Conclusion
In this blog post, we discussed the steps to create bUnit tests for Blazor components and how to integrate them in an Azure Pipeline. Try out these steps for yourself and leave your feedback in the comments section below.
For more details refer, https://bUnit.egilhansen.com
Try our 65+ Blazor UI components by downloading the free 30-day trial or NuGet package, and feel free to have a look at our documentation, GitHub, and online examples to explore other available features.
You can also contact us through our support forum, Direct-Trac, or feedback portal. As always, we would like to hear from you!
Comments (4)
I followed all of the above steps and am still getting this error. Any idea on what to do in order to solve this issue?
Message:
System.InvalidOperationException : Cannot provide a value for property ‘Localizer’ on type ‘Syncfusion.Blazor.Popups.SfDialog’. There is no registered service of type ‘Syncfusion.Blazor.ISyncfusionStringLocalizer’.
Hi Marcus, We have addressed this issue at the earliest and done it in volume 2 release. Please upgrade Syncfusion.Blazor nuget.
How do I call “Find” function by using CSS Selector ID? For example I want to check the section that I wrote like that
bla bla bla
and try to find it on my test case like that
var somethingDiv = cur.Find(“something”);
However it throws an ElementNotFoundException: “Bunit.ElementNotFoundException: No elements were found that matches the selector ‘something'”
What am I doing wrong?
Hi Yosef Hamerahef,
Thanks! for following my blog.
Consider an element like the following,
Then you can get the element by using CSS selector ID by concatenating # before the ID like
var somethingDiv = cur.Find(“#Something”);
Thanks,
Ramkumar Shanmugam
Comments are closed.