Blazor testing may just be the biggest potential upside of the framework. Learn about the core concepts of Blazor testing that will help you make bulletproof apps.
Blazor is a new web application framework that promises to deliver native web client experiences without the need for writing JavaScript. Developers can comfortably build full stack web applications with the .NET framework and tools. Many highlight this .NET foundation as Blazor’s strength, however the story of Blazor testing may just be its biggest potential upside. In this article we’ll discuss the core concepts that make Blazor an ideal candidate for testing and the tools that support: Unit Testing, Integration Testing, and Automated System Testing.
Because C# is a compiled language and utilizes strongly typed classes, it already has a lot going for it from a software stability perspective. Trivial errors are immediately caught by the compiler as Intellisense helps guide. Now expand these capabilities full stack where not only server logic benefits, but so does your web client’s logic and UI component code. In addition to these fundamentals we’ll add in Unit Tests, Integration Tests, Component Tests, Mocking and Automated System Tests across the entire application without switching languages or methodologies.
When it comes to the .NET ecosystem there’s a lot of offerings to get the job done. Below are tools that I’ve used personally throughout my career and had great success with. Except for bUnit, these tools have existed prior to Blazor’s creation. Since Blazor release these tools have expanded their usefulness to include the new .NET franchise.
Unit testing is the first chance to ensure individual pieces of business logic are working as designed. Unit testing is typically executed by the developer before committing a feature to a larger codebase. These concise tests help find issues early on. When used in conjunction with debugging, finding and fixing issues takes much less time than if a bug is discovered later in the application development process. To further enhance productivity, it’s important that the testing framework require the least amount of setup and configuration while tightly integrating with the development environment.
Test Target |
Business Logic, View Models |
Cost |
|
License |
Open Source |
Type |
Framework |
Interface: Test Runner |
Visual Studio or CLI compatible |
xUnit has a long history in the .NET community and is a natural fit for all .NET projects including Blazor. xUnit.net is a free, open source, community-focused unit testing tool for .NET. Written by the original inventor of NUnit v2, xUnit.net is the latest technology for unit testing C#, F#, VB.NET and other .NET languages.
One advantage of xUnit is its overall simplicity as tne xUnit template is included in the .NET Core cross-platform development workload. Adding an xUnit test project to your application is as simple as right clicking a solution and choosing Add > New Project > xUnit Test Project from the menu in Visual Studio, as seen in Figure 1. Alternatively, you can run $ dotnet new xunit from the command line.
Figure 1: Create a new project dialog with xUnit.
The minimum requirements for defining a unit test are adding a [Fact] attribute to the desired method which includes an Assert statement. Facts are tests which are always true and test invariant conditions, while Assert is used to verify that the conditions have been met by the test. Fact and Assert are used in the example below to test the Add method. In the PassingTest example, the Assert validates the expected output of 4 against Add(2,2). To demonstrate a failing test, if the expected output were 5, the Assert would fail.
public class Class1
{
[Fact]
public void PassingTest()
{
Assert.Equal(4, Add(2, 2));
}
[Fact]
public void FailingTest()
{
Assert.Equal(5, Add(2, 2));
}
int Add(int x, int y)
{
return x + y;
}
}
Since Blazor applications utilize .NET for the client and server, both server and client share logic. xUnit is ideal for the shared layer of a Blazor application as the code here has a generalized purpose. As seen in Figure 2 below, we can reference the entire application from our xUnit project to test common code.
Figure 2: Adding project references.
In the following example we’ll test validation logic that can be used to validate a correctly formatted email address. The email validator could be used for checking UI input on the client, validating a web endpoint post, or safeguarding a database record about to be written on the sever.
[Fact(DisplayName = "Can validate correctly formatted email address")]
public void ValidateCorrectlyFormattedEmail()
{
// Arrange
var validEmail = "fake@domain.com";
var uut = new EmailValidator();
// Act
var isValid = uut.Validate(validEmail);
// Assert
Assert.True(isValid);
}
In a traditional web application written with a JavaScript client this would not be possible. Instead a separate validator and test would need to be created for the JavaScript client using a completely different tool chain. The strengths of a full stack .NET application aren’t just seen in shared logic either—with Blazor component testing we can test the individual UI components too.
Web developers are accustomed to the instant feedback the browser provides. Many frameworks even include built in mechanics for refreshing the application in real-time, or “hot reloading.” Hot reloading is often used to quickly prototype components or features for a given application. While hot reloading is certainly a tool that has its place in web development, it’s possible it is relied upon too much. Blazor has yet to provide such a tool although one is in development. An arguably better alternative is unit testing UI components. A UI component can be isolated, tested and quickly iterated upon just as application logic can be. Through unit testing we can achieve quick results without the overhead of loading the application, or even the browser. To test a Blazor component we’ll use a new framework called bUnit which is specifically designed for this purpose.
Test Target |
UI Component |
Cost |
|
License |
Open Source |
Type |
Framework |
Interface: Test Runner |
Visual Studio or CLI compatible |
bUnit is a testing library for Blazor Components. Its goal is to make it easy to write comprehensive, stable unit tests. bUnit builds on top of existing unit testing frameworks such as xUnit, NUnit, and MSTest, which runs the Blazor components test as any normal unit test. bUnit runs a test in milliseconds, compared to browser-based UI tests, where a test usually takes seconds to run.
bUnit can setup and define components under tests using either C# or Razor syntax. bUnit includes methods to verify component rendering using a semantic HTML comparer. Parameters, cascading values and injecting services into components under are made easy with bUnit’s comprehensive helper methods. Even triggering event handlers and exercising a component’s interactive features is supported by the library.
Adding a bUnit test project to your application can be done several ways. One of the easiest solutions is to install the bUnit project template and create a new project using the command line.
dotnet new --install bunit.template
dotnet new bunit -o <NAME OF TEST PROJECT>
With the bUnit project created we’ll add our project or component library as a reference.
Now we can test individual components in isolation with bUnit. Since we’re already familiar with xUnit, we’ll continue with the xUnit [Fact] conventions and write our first bUnit test. A common component test is to ensure that a component initializes correctly and renders the appropriate HTML markup. This can be accomplished with bUnit’s RenderComponent<TComponent> method.
Component Unit Test Example: Initialization and Rendering
In the following example we’ll test the initial rendered Alert component. The component should render a styled div element with an internal close button. The expected rendered HTML is declared as the string expectedMarkup. Since bUnit relies on “semantic HTML comparison,” the expectedMarkup doesn’t need to be exact match, but rather an HTML equivalent match. Aspects that don’t affect component behavior or visual representation are ignored, such as: comments, insignificant white space, css class order, and implicit attributes, just to name a few.// bUnit library
using Bunit;
// xUnit framework
using Xunit;
// Component library or Blazor application
using TestableBlazor.Client;
[Fact(DisplayName = "Initial Alert renders correct markup")]
public void AlertInit()
{
// Render an Alert component
var cut = RenderComponent<Alert>();
// Expected HTML rendered
string expectedMarkup = @"
<div class=""alert alert-danger"">
<button type=""button"" class=""close"" aria-label=""Close"">
<span aria-hidden=""true"">×</span>
</button>
</div>";
// Did it match?
cut.MarkupMatches(expectedMarkup);
}
In this test an instance of the Alert component is created, and the component lifecycle is fully completed. The final rendered component markup is then checked against the expected markup.
Component Unit Test Example: Parameter Set
bUnit can go beyond initialization rendering tests and test specific sections of markup based on component parameters. In the next example we’ll assume we have a component that has two distinct color themes represented by a CSS class. We can toggle the theme by setting the IsInfo parameter to change the CSS class. The following example renders the component with the parameter set to true and checks only the CSS class attribute on the rendered component.
[Fact(DisplayName = "Alert has info theme")]
public void AlertColor()
{
// Render the component with IsInfo = true
var cut = RenderComponent<Alert>(parameters =>
parameters.Add(p => p.IsInfo, true));
// The expected CSS value
var expectedCss = "alert alert-info";
// The actual CSS value
var actualCss = cut.Find("div").GetAttribute("class");
// Did it match?
Assert.Equal(expectedCss, actualCss);
}
In this test an instance of the Alert component is created, and the component’s IsInfo parameter set true. The rendered markup is then checked for the class attribute and the value is validated against the expected CSS class.
Simple component unit tests like these can quickly accelerate the development process. Since the application and browser are not involved the process is much faster and accurate versus manually loading the application and checking the rendered output visually.
Some components require external dependencies, such as data bound components. In addition, components can require other components, or be assembled into tightly coupled views like a form. Whether we’re writing complex unit tests, or integration tests, we need additional help setting the stage with controlled dependencies. As we expand the Blazor application’s testing capabilities it’s helpful to have a mocking library that works well with our test environment.
Mocking is a process used in unit testing when the unit being tested has external dependencies. The purpose of mocking is to isolate and focus on the code being tested and not on the behavior or state of external dependencies. In mocking, the dependencies are replaced by closely controlled replacement objects that simulate the behavior of the real ones.
A mock is a sophisticated substitute that will still return a predetermined value. It can also be programmed with expectations in terms of how many times each method should be called, in which order and with what data.
JustMock
Integration |
xUnit, bUnit |
Cost |
|
License |
Open Source |
Type |
Framework |
Interface: Test Runner |
Visual Studio or CLI compatible |
*Included with some Telerik DevCraft Licenses |
Telerik JustMock is a library which can create a Mock with just one line of code, making complex tests easy to arrange.
The free version of Telerik JustMock can be installed through NuGet. In addition, a commercial version with advanced features is also available.
dotnet add package JustMock --version 2020.3.1019.2
With the JustMock dependency added to our test project we can quickly begin mocking services required by components.
Component Unit Test Example: Mocking Injected Services
When using or testing Blazor components it’s common to see the following error: “There is no registered service of type 'T'. This usually means a service has not registered with dependency injection. Given a component under test that uses dependency injection shown in the following example, rendering a component under test without a dependency will result in error.
@* Component: Index.razor *@
@inject IDataService DataService
protected override async Task OnInitializedAsync()
{
Model.Regions = await DataService.GetRegions();
}
// Unit Test
var cut = RenderComponent<Index>();
// Error: There is no registered service of type 'IDataService'
In the case of component unit testing, we still need to register a service and should do so with a means of isolating the results from that service to avoid nuances in non-static data that can influence test. Using JustMock we can complete the test by mocking the IDataService interface directly. In the following unit test code, an IDataService mock is created by calling Mock.Create. With the mock created we can then use JustMock to arrange the method IDataService.GetRegions so it returns a predictable response. With the mock arranged the mocked service is then added to bUnit’s dependency injection container.
private void AddMockDataService()
{
var mockDataService = Mock.Create<IDataService>();
Mock.Arrange(() => mockDataService.GetRegions())
.Returns(
Task.FromResult(new string[] { "AA", "BB", "CC", "DD" })
);
// Register service with bUnit
Services.AddSingleton(mockDataService);
}
With the service available to the component, unit tests can now be executed to test functionality. In the following test example, the Index component is rendered, then the component’s Model property is checked to see if it successfully calls the mocked service and populates with data and the first data item is selected by default.
[Fact(DisplayName = "Region first item is selected on initialization.")]
public void SettingRegionSelectsFirstItem()
{
AddMockDataService()
// Render the index page
// Component internally calls IDataService.GetRegions()
// and IDataService.GetTeamsByRegion()
var cut = RenderComponent<Index>();
// Get the Model property from the component Instance
var cutModel = cut.Instance.Model;
// Did our Model bind?
// Was the Regions collection filled?
Assert.Collection(cutModel.Regions,
i => i.Contains("AA"),
i => i.Contains("BB"),
i => i.Contains("CC"),
i => i.Contains("DD"));
// Was the first Region selected by default?
Assert.Equal("AA", cut.Instance.Model.SelectedRegion);
}
After each component is thoroughly tested individually, integration between components can be tested to ensure they perform specific tasks or activities.
Component Integration Test Example: Cascading Drop Downs
Testing components as group through integration testing ensures that features of an application behave as expected and communication between units are faultless. In the following example we’ll test the integration between the component life cycle, form model and multiple drop-down components. In a cascading drop down scenario, one list influences the results of another by fetching new data when the primary list is changed.
@inject TestableBlazor.Client.Services.IDataService DataService
<EditForm Model="Model" OnValidSubmit="HandleValidSubmit">
<div class="form-group">
<label for="regionSelect">Region</label>
<TelerikDropDownList Value="Model.SelectedRegion"
ValueChanged="@((string s) => SelectedRegionChanged(s))"
ValueExpression="@(() => Model.SelectedRegion)"
Data="Model.Regions"
Id="regionSelect"
Class="form-control"
Width="100%" />
</div>
<div class="form-group">
<label for="teamSelect">Team</label>
<TelerikDropDownList @bind-Value="Model.SelectedTeam"
Data="Model.Teams"
Id="teamSelect"
Class="form-control"
Width="100%" />
</div>
<TelerikButton Id="formSubmit" Primary="true">Submit</TelerikButton>
<TelerikButton Id="formReset" ButtonType="ButtonType.Reset">Cancel</TelerikButton>
</EditForm>
@code {
[Parameter]
public UserFormViewModel Model { get; set; } = new UserFormViewModel();
public async Task SelectedRegionChanged(string value)
{
Model.SelectedRegion = value;
Model.Teams = await DataService.GetTeamsByRegion(Model.SelectedRegion);
Model.SelectedTeam = Model.Teams[0];
}
protected override async Task OnInitializedAsync()
{
// init here
Model.Regions = await DataService.GetRegions();
await SelectedRegionChanged(Model.Regions[0]);
}
}
The following test will verify that component’s Model property has the correct SelectedTeam value when the primary drop down, SelectedRegion is changed. To do this JustMock provides a reliable output from IDataService.GetTeamsByRegion.
Mock.Arrange(() => mockDataService.GetTeamsByRegion(Arg.AnyString))
.Returns(
(string region) => Task.FromResult(
new string[] { $"Red {region}", $"Green {region}", $"Blue {region}" })
);
bUnit is then used to invoke the drop-down’s change event simulating form interaction.
[Fact(DisplayName = "Selecting a region selects the first team value.")]
async Task SettingRegionSelectsFirstTeam()
{
// Render the index component
var cut = RenderComponent<Index>();
// Invoke a drop-down change
await cut.Instance.SelectedRegionChanged("BB");
// Is the model the correct selected team?
Assert.Equal("Red BB", cut.Instance.Model.SelectedTeam);
}
This test confirms that the correct methods are invoked on IDataServce when the form is initialized, and the drop-down is exercised. It also confirms that the Model persists the correct value from the child drop-down through two-way databinding.
Integrated tests can be conducted by either developers or independent testers and are usually comprised of a combination of automated functional and manual tests. Testing with xUnit, bUnit, and JustMock can cover many of the developer conducted tests, while automated system testing can benefit from additional tooling.
System testing is a testing method used to evaluate the completed and integrated system. When testing a Blazor application, a system test proves the end-to-end functionality of the software is tested which may include cross-browser compatibility, authentication, live services, and much more. Designing such tests and executing them in an automated way may sound daunting, but the right tool for the job can greatly decrease the overhead and cost.
Cost |
|
License |
Commercial |
Type |
Integrated Test Environment |
Interface: Integrated Test Environment (ITE) |
Manage & Edit |
Interface: Test Recorder |
Browser Plugin |
Interface: Test Runner |
ITE, Visual Studio or CLI compatible |
Interface: CI/CD |
Azure DevOps, Jenkins, Bamboo, … compatible |
* Dev Edition Included with some Telerik DevCraft Licenses |
Telerik Test Studio is a commercial Automated System Testing suite of tools with built-in integration with Telerik UI for Blazor. Test Studio includes both codeless and code-based automation capabilities in a modular framework to enable testing anyone can use regardless of expertise and delivers personalized results for both QA and developers. Tests created in Test Studio, both recorded and code-based, are native C# (.NET) code which can be edited within Test Studio's ITE editor or with the Visual Studio Plugin.
Building tests manually is a time-consuming process—with Test Studio the busy work of building automated tests can be greatly reduced through its intuitive test recorder. Simply record your test and use automated playback for an easy and fast way to craft your test and then run that same script to test multiple browsers. The Test Studio recorder/player can be used as a stand-alone application, or Visual Studio plugin. The stand-alone application is ideal for Quality Assurance (QA) as they’re not required to use Visual Studio (VS), thereby freeing up VS licenses and allowing QA to focus on the task at hand. As of R3 2020 Test Studio shipped with a new even more intuitive Recorder UI design, helping the user to focus on the most needed/useful actions during recording, along with providing further speed and productivity optimizations for a quick and easy recording experience.
Automated Test Example: Form IO
There are many test scenarios that could be created for a form. We could validate the initial state of the form, ensure all the labels are rendered, default values are set, and verify validation on submission of a default form. We can also exercise the form, checking its inputs and outputs and validating a successful submission. As shown in Figure 3 the input/output scenario is recording a test that:
When the form’s submit button is exercised, we can also instruct our test to validate the output or completed state of the form, as seen in Figure 4:
Figure 4: The thank you message shown after a form is submitted.
Recording the test with the Test Studio application or VS plugin is as simple as point and click. We’ll start recording a test as seen in Figure 5 by:
Test Studio will navigate to the application under test and begin recording user interactions such as mouse and keyboard events. The test recorder toolbar is displayed in browser which can launch an optional test control panel. The test recorder toolbar works as an element selection tool where the user can simply hover over HTML elements for a contextually aware menu of potential test steps. As seen in Figure 6, from the test recorder toolbar advanced recording tools can be used to inspect the DOM tree and build verifications like OCR validations, style validations, etc.
Figure 6: The test recorder toolbar assists with recording tests for automation.
If the application makes use of Telerik UI for Blazor, Test Studio will detect the UI components and display their corresponding properties and values. Shown in Figure 7, we can see the test inspection tool list possible verifications.
Figure 7: Test recorder integration with Telerik UI for Blazor.
After each step is recorded and the browser is closed, a list of all test steps is displayed in Test Studio where they can be fine-tuned (by GUI or C# code), removed, or executed. In the following example, we can see the test created by the recorder with verification steps for validating various inputs and outputs of the form, as shown in Figure 8.
Figure 8: A recorded test, and completed automated test in Test Studio.
This is just one example of how Test Studio can be used to test a Blazor application. The Test Studio suite goes well beyond the scope of this article with features for:
The examples shown in this article highlight four tools that cover the full spectrum of tests generally used in application stability testing. Blazor and the .NET ecosystem provide a solid base for unit testing, integration testing and automated system testing. Unit tests with xUnit provide a quick feedback loop for testing common logic, while bUnit expands this ability to Blazor components. As the application testing progresses into integration tests, Telerik JustMock delivers an easy to use API set for mocking reliable dependencies. When it comes to automated system testing, Telerik Test Studio embraces the .NET stack and further adds value with tight integration with Telerik UI for Blazor. These testing tools not only cover their intended niche but are robust enough to provide some overlap. Adopting these Blazor stability testing tools is key to bulletproofing your next Blazor application.
Ed