Table of Contents

Verifying markup from a component

When a component is rendered in a test, the result is a IRenderedFragment or a IRenderedComponent<TComponent>. Through these, it is possible to access the rendered markup (HTML) of the component and, in the case of IRenderedComponent<TComponent>, the instance of the component.


An IRenderedComponent<TComponent> inherits from IRenderedFragment. This page will only cover features of the IRenderedFragment type. IRenderedComponent<TComponent> is covered on the Verifying the state of a component under test page.

This page covers the following verification approaches:

  • Basic verification of raw markup
  • Semantic comparison of markup
  • Inspecting the individual DOM nodes in the DOM tree
  • Finding expected differences in markup between renders

The following sections will cover each of these.

Basic verification of raw markup

To access the rendered markup of a component, just use the Markup property on IRenderedFragment. This holds the raw HTML from the component as a string.


Be aware that all indentions and whitespace in your components (.razor files) are included in the raw rendered markup, so it is often wise to normalize the markup string a little. For example, via the string Trim() method to make the tests more stable. Otherwise, a change to the formatting in your components might break the tests unnecessarily when it does not need to.

To avoid these issues and others related to asserting against raw markup, use the semantic HTML comparer that comes with bUnit, described in the next section.

To get the markup as a string, do the following:

  var renderedMarkup = cut.Markup;
  Assert.Equal("<h1>Hello world from Blazor</h1>", renderedMarkup);

You can perform standard string assertions against the markup string, like checking whether it contains a value or is empty.

Semantic comparison of markup

Working with raw markup only works well with very simple output, but even then you have to sanitize it to get stable tests. A much better approach is to use the semantic HTML comparer that comes with bUnit.

How does the semantic HTML comparer work?

The comparer takes two HTML fragments (e.g. in the form of a C# string) as input, and returns true if both HTML fragments result in the same visual rendered output in a web browser. If not, it returns false.

For example, a web browser will render this HTML:

<span>Foo Bar</span>

This will be done in exactly the same way as this HTML:

  Foo       Bar

This is why it makes sense to allow tests to pass, even when the rendered HTML markup is not entirely identical to the expected HTML from a normal string comparer's perspective.

bUnit's semantic HTML comparer safely ignores things like insignificant whitespace and the order of attributes on elements, as well as many more things. This leads to much more stable tests, as - for example - a reformatted component doesn't break its tests because of insignificant whitespace changes. More details of the semantic comparer can be found on the Customizing the semantic HTML comparison page.

The MarkupMatches() method

The HTML comparer can be easily accessed through MarkupMatches() extension methods, available in places that represent HTML fragments in bUnit, i.e. on IRenderedFragment and the INode and INodeList types.

In the following examples, the <Heading> component listed below will be used as the component under test.

<h3 id="heading-1337" required>
  Heading text
  <small class="text-muted mark">
    Secondary text

To use the MarkupMatches() method to perform a semantic comparison of the output of the <Heading> component through its IRenderedFragment, do the following:

  cut.MarkupMatches(@"<h3 id=""heading-1337"" required>
                          Heading text
                          <small class=""mark text-muted"">Secondary text</small>

The highlighted line shows the call to the MarkupMatches() method. This test passes even though the insignificant whitespace is not exactly the same between the expected HTML string and the raw markup produced by the <Heading> component. It even works when the CSS class list is not in the same order on the <small> element.

The MarkupMatches() method is also available on INode and INodeList types, for example:

  var smallElm = cut.Find("small");
  smallElm.MarkupMatches(@"<small class=""mark text-muted"">Secondary text</small>");

Here we use the Find(string cssSelector) method to find the <small> element, and only verify it and its content and attributes.


Working with Find(), FindAll(), INode and INodeList is covered later on this page.

Text content can also be verified with the MarkupMatches() method, e.g. the text inside the <small> element. It has the advantage over regular string comparison in that it removes insignificant whitespace in the text automatically - even between words - where a normal string Trim() method isn't enough. For example:

  var smallElmText = cut.Find("small").TextContent;
  smallElmText.MarkupMatches("Secondary text");

The semantic HTML comparer can be customized to make a test case even more stable and easier to maintain. For example, it is possible to ignore an element or attribute during comparison, or provide a regular expression to the comparer when comparing a specific element or attribute to make the comparer work with generated data.

Learn more about the customization options on the Customizing the semantic HTML comparison page.

Inspecting DOM nodes

The rendered markup from a component is available as a DOM node through the Nodes property on IRenderedFragment, as well as the Find(string cssSelector) and FindAll(string cssSelector) extension methods on IRenderedFragment.

The Nodes property and the FindAll() method return an AngleSharp INodeList type, and the Find() method returns an AngleSharp IElement type.

The DOM API in AngleSharp follows the W3C DOM API specifications and gives you the same results as a state-of-the-art browser’s implementation of the DOM API in JavaScript. Besides the official DOM API, AngleSharp and bUnit add some useful extension methods on top. This makes working with DOM nodes convenient.

Finding nodes with the Find() and FindAll() methods

Users of the famous JavaScript framework jQuery will recognize these two methods:

Let's see some examples of using the Find(string cssSelector) and FindAll(string cssSelector) methods to query the <FancyTable> component listed below.

  <caption>Lorem lipsum captium</caption>
      <td style="white-space:nowrap">Foo</td>
      <td style="white-space:nowrap">Baz</td>

To find the <caption> element and the first <td> elements in each row, do the following:

var tableCaption = cut.Find("caption");
var tableCells = cut.FindAll("td:first-child");

Once you have one or more elements, you verify against them, such as by inspecting their properties through the DOM API. For example:

  Assert.Equal(2, tableCells.Count);
  Assert.All(tableCells, td => td.HasAttribute("style"));

Auto-refreshing Find() queries

An element found with the Find(string cssSelector) method will be updated if the component it came from is re-rendered.

However, that does not apply to elements that are found by traversing the DOM tree via the Nodes property on IRenderedFragment, for example, as those nodes do not know when their root component is re-rendered. Consequently, they don’t know when they should be updated.

As a result of this, it is always recommended to use the Find(string cssSelector) method when searching for a single element. Alternatively, always reissue the query whenever you need the element.

Auto-refreshable FindAll() queries

The FindAll(string cssSelector, bool enableAutoRefresh = false) method has an optional parameter, enableAutoRefresh, which when set to true will return a collection of IElement. This automatically refreshes itself when the component the elements came from is re-rendered.

Finding expected differences

It can sometimes be easier to verify that an expected change, and only that change, has occurred in the rendered markup than it can be to specify how all the rendered markup should look after re-rendering.

bUnit comes with a number of ways for finding lists of IDiff; the representation of a difference between two HTML fragments. All of these are direct methods or extension methods on the IRenderedFragment type or on the INode or INodeList types:

In addition to this, there are a number of experimental assertion helpers for IDiff and IEnumerable<IDiff>, making it easier and more concise to declare your assertions.

Let's look at a few examples of using the assertion helpers. In the first one, we will use the <Counter> component listed below:


    Current count: @currentCount

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
  int currentCount = 0;

  void IncrementCount()

Here is an example of using the GetChangesSinceFirstRender() method:

  // Act - increment the counter

  // Assert - find differences between first render and click
  var diffs = cut.GetChangesSinceFirstRender();

  // Only expect there to be one change
  var diff = diffs.ShouldHaveSingleChange();
  // and that change should be a text
  // change to "Current count: 1"
  diff.ShouldBeTextChange("Current count: 1");

This is what happens in the test:

  • On line 8, GetChangesSinceFirstRender() is used to get a list of differences.
  • On line 11, the ShouldHaveSingleChange() method is used to verify that there is only one change found.
  • On line 14, the ShouldBeTextChange() method is used to verify that the single IDiff is a text change.

Testing a more complex life cycle of a component can be done more easily using the GetChangesSinceSnapshot() and SaveSnapshot() methods along with a host of other assert helpers.

This example tests the <CheckList> component listed below. The component allows you to add new items to the checklist by typing into the input field and hitting the enter key. Items can be removed from the list again by clicking on them.

<input type="text" placeholder="Add new item" 
       @onkeyup="OnTextInput" />
  @foreach (var item in items)
    <li @onclick="() => items.Remove(item)">@item</li>
  private string newItemValue = string.Empty;
  private List<string> items = new List<string>();

  private void OnTextInput(KeyboardEventArgs args)
    if(args.Key == "Enter")
      newItemValue = string.Empty;

To test the end-to-end life cycle of adding and removing items from the <CheckList> component, do the following:

var cut = RenderComponent<CheckList>();
var inputField = cut.Find("input");

// Add first item
inputField.Change("First item");
inputField.KeyUp(key: "Enter");

// Assert that first item was added correctly
var diffs = cut.GetChangesSinceFirstRender();
  .ShouldBeAddition("<li>First item</li>");

// Save snapshot of current DOM nodes

// Add a second item
inputField.Change("Second item");
inputField.KeyUp(key: "Enter");

// Assert that both first and second item was added
// since the first render
diffs = cut.GetChangesSinceFirstRender();
  diff => diff.ShouldBeAddition("<li>First item</li>"),
  diff => diff.ShouldBeAddition("<li>Second item</li>")

// Assert that only the second item was added
// since the call to SaveSnapshot()
diffs = cut.GetChangesSinceSnapshot();
  .ShouldBeAddition("<li>Second item</li>");

// Save snapshot again of current DOM nodes

// Click last item to remove it from list

// Assert that the second item was removed
// since the call to SaveSnapshot()
diffs = cut.GetChangesSinceSnapshot();

This is what happens in the test:

  1. First the component is rendered and the input field is found.
  2. The first item is added through the input field.
  3. The GetChangesSinceFirstRender(), ShouldHaveSingleChange() and ShouldBeAddition() methods are used to verify that the item was correctly added.
  4. The SaveSnapshot() is used to save a snapshot of current DOM nodes internally in the cut. This reduces the number of diffs found in the following steps, simplifying verification.
  5. A second item is added to the check list.
  6. Two verifications are performed at this point, one using the GetChangesSinceFirstRender() method which finds two changes, and one using the GetChangesSinceSnapshot() method, which finds a single change. The first is only done for illustrative purposes.
  7. A new snapshot is saved, replacing the previous one with another call to the SaveSnapshot() method.
  8. Finally the last item in the list is found and clicked, and the GetChangesSinceSnapshot() method is used to find the changes, a single diff, which is verified as a removal of the second item.

As mentioned earlier, the IDiff assertion helpers are still experimental. Any feedback and suggestions for improvements should be directed to the related issue on GitHub.

Progress Telerik

Premium sponsor: Progress Telerik.


Editorial support provided by Packt.

.NET Foundation

Supported by the .NET Foundation.