Table of Contents

Writing tests for Blazor components

Testing Blazor components is a little different from testing regular C# classes: Blazor components are rendered, they have the Blazor component life cycle during which we can provide input to them, and they can produce output.

Use bUnit to render the component under test, pass in its parameters, inject required services, and access the rendered component instance and the markup it has produced.

Rendering a component happens through bUnit's TestContext. The result of the rendering is an IRenderedComponent, referred to as a "rendered component", that provides access to the component instance and the markup produced by the component.

Write tests in .cs or .razor files

bUnit works with MSTest, NUnit, and xUnit, and it allows you to write the unit tests in either .cs or .razor files.

The latter, writing tests in .razor files, provides an easier way to declare component markup and HTML markup in the tests, so it will most likely be the go-to for many people in the future.

However, the current Razor editor in Visual Studio 2022 does not offer all the code editing features available in the C# editor, so that is something to consider if you choose to write tests in .razor files.

The following sections show how to get started writing tests in either .cs or .razor files.

Creating basic tests in .razor files

Before writing tests in .razor files, a few things needs to be in place:

  1. Make sure the test project has the SDK type set to Microsoft.NET.Sdk.Razor. Otherwise the Blazor compiler will not translate your .razor files into runnable code.

  2. Add an _Imports.razor file to the test project. It serves the same purpose as _Imports.razor files in regular Blazor projects. These using statements are useful to add right away:

    @using Microsoft.AspNetCore.Components.Forms
    @using Microsoft.AspNetCore.Components.Web
    @using Microsoft.JSInterop
    @using Microsoft.Extensions.DependencyInjection
    @using AngleSharp.Dom
    @using Bunit
    @using Bunit.TestDoubles
    

Also add an using statement for your general purpose testing framework, e.g. @using Xunit for xUnit.

With that in place, lets look at a simple example that tests the following <HelloWorld> component:

<h1>Hello world from Blazor</h1>
@inherits TestContext
@code
{
  [Fact]
  public void HelloWorldComponentRendersCorrectly()
  {
    // Act
    var cut = Render(@<HelloWorld/>);

    // Assert
    cut.MarkupMatches(@<h1>Hello world from Blazor</h1>);
  }
}

The test above does the following:

  1. Inherits from the bUnit TestContext. This base class offers the majority of functions.
  2. Renders the <HelloWorld> component using TestContext, which is done through the Render(RenderFragment) method. We cover passing parameters to components on the Passing parameters to components page.
  3. Verifies the rendered markup from the <HelloWorld> component using the MarkupMatches method. The MarkupMatches method performs a semantic comparison of the expected markup with the rendered markup.
Tip

Learn more about how the semantic HTML/markup comparison in bUnit works, and how to customize it, on the Customizing the semantic HTML comparison page.

Tip

In bUnit tests, we like to use the abbreviation CUT, short for "component under test", to indicate the component that is being tested. This is inspired by the common testing abbreviation SUT, short for "system under test".

Secret sauce of '.razor' files tests

The trick employed in these tests is the "inline Razor templates syntax", i.e. where a render fragment is simply created using the @<{HTML tag}>...</{HTML tag}> notation. In that notation there is no need to do any escaping of e.g. the quotation mark ("), that is usually associated with working with markup in C# code.

One small caveat to be aware of is that the inline Razor templates syntax only supports one outer element, e.g. this is OK:

@<Foo>
   <Bar>
     <Baz>...</Baz>
   </Bar>
 </Foo>

However, this will not work:

@<Foo></Foo>
 <Bar></Bar>

There is a simple workaround though: wrap all elements in the special Blazor element <text>. The <text> element will not be part of the rendered output, but it provides a simple way to group multiple root elements into a single inline Razor template. E.g.:

@<text>
   <Foo></Foo>
   <Bar></Bar>
 </text>

Creating basic tests in .cs files

This is a simple example of writing tests in .cs files which tests the following <HelloWorld> component:

<h1>Hello world from Blazor</h1>
using Xunit;
using Bunit;

public class HelloWorldTest : TestContext
{
  [Fact]
  public void HelloWorldComponentRendersCorrectly()
  {
    // Act
    var cut = RenderComponent<HelloWorld>();

    // Assert
    cut.MarkupMatches("<h1>Hello world from Blazor</h1>");
  }
}

The test above does the following:

  1. Inherits from the bUnit's TestContext. This base class offers the majority of functions.
  2. Renders the <HelloWorld> component using TestContext, which is done through the RenderComponent<TComponent>(Action<ComponentParameterCollectionBuilder<TComponent>>?) method. We cover passing parameters to components on the Passing parameters to components page.
  3. Verifies the rendered markup from the <HelloWorld> component using the MarkupMatches method. The MarkupMatches method performs a semantic comparison of the expected markup with the rendered markup.
Tip

Learn more about how the semantic HTML/markup comparison in bUnit works, and how to customize it, on the Customizing the semantic HTML comparison page.

Tip

In bUnit tests, we like to use the abbreviation CUT, short for "component under test", to indicate the component that is being tested. This is inspired by the common testing abbreviation SUT, short for "system under test".

Instantiate TestContext in each test

If you prefer to instantiate TestContext in each test, instead of inheriting from it, you can do so. This can be useful if you have your own base class that you want to inherit from, or if you want to use a different test framework than the ones listed here.

Just be aware that all examples in the rest of the documentation assumes that you are inheriting from TestContext, so adjust accordingly.

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>");
  }
}
Progress Telerik

Premium sponsor: Progress Telerik.

Packt

Editorial support provided by Packt.

.NET Foundation

Supported by the .NET Foundation.