-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathverify-markup.html
More file actions
251 lines (225 loc) · 20 KB
/
verify-markup.html
File metadata and controls
251 lines (225 loc) · 20 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Verifying markup from a component | bUnit </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Verifying markup from a component | bUnit ">
<meta name="description" content="bUnit is a unit testing library for Blazor Components. You can easily define components under test in C# or Razor syntax and verify outcome using semantic HTML diffing/comparison logic. You can interact with and inspect components, trigger event handlers, provide cascading values, inject services, mock IJsRuntime, and perform snapshot testing.">
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
<link rel="icon" href="/favicon.ico">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<link rel="manifest" href="/site.webmanifest">
<meta name="msapplication-TileColor" content="#da532c">
<meta name="theme-color" content="#ffffff">
<link rel="manifest" href="/site.webmanifest">
<link rel="stylesheet" href="../../public/docfx.min.css">
<link rel="stylesheet" href="../../public/main.css">
<meta name="docfx:navrel" content="../../toc.html">
<meta name="docfx:tocrel" content="../toc.html">
<meta name="docfx:rel" content="../../">
<meta name="docfx:docurl" content="https://github.com/bUnit-dev/bUnit/blob/stable/docs/site/docs/verification/verify-markup.md/#L1">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for "{query}"">
<meta name="loc:searchNoResults" content="No results for "{query}"">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
</head>
<script type="module" src="./../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../index.html">
<img id="logo" class="svg" src="../..//images/bunit-logo.png" alt="bUnit">
bUnit
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled="" placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" style="margin-top: -.65em; margin-left: -.8em" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="verify-markup">
<h1 id="verifying-markup-from-a-component">Verifying markup from a component</h1>
<p>Generally, the strategy for verifying markup produced by components depends on whether you are creating reusable component library or a single-use Blazor app component.</p>
<p>With a <strong>reusable component library</strong>, the markup produced may be considered part of the externally observable behavior of the component, and that should thus be verified, since users of the component may depend on the markup having a specific structure. Consider using <code>MarkupMatches</code> and semantic comparison described below to get the best protection against regressions and good maintainability.</p>
<p>When <strong>building components for a Blazor app</strong>, the externally observable behavior of components are how they visibly look and behave from an end-users point of view, e.g. what the user sees and interact with in a browser. In this scenario, consider use <code>FindByLabelText</code> and related methods described below to inspect and assert against individual elements look and feel, for a good balance between protection against regressions and maintainability. Learn more about this testing approach at <a href="https://testing-library.com">https://testing-library.com</a>.</p>
<p>This page covers the following <strong>verification approaches:</strong></p>
<ul>
<li>Inspecting the individual DOM nodes in the DOM tree</li>
<li>Semantic comparison of markup</li>
<li>Finding expected differences in markup between renders</li>
<li>Verification of raw markup</li>
</ul>
<p>The following sections will cover each of these.</p>
<h2 id="result-of-rendering-components">Result of rendering components</h2>
<p>When a component is rendered in a test, the result is a <a class="xref" href="../../api/Bunit.IRenderedComponent-1.html">IRenderedComponent<TComponent></a> or a <a class="xref" href="../../api/Bunit.IRenderedComponent-1.html">IRenderedComponent<TComponent></a>. Through these, it is possible to access the rendered markup (HTML) of the component and, in the case of <a class="xref" href="../../api/Bunit.IRenderedComponent-1.html">IRenderedComponent<TComponent></a>, the instance of the component.</p>
<div class="NOTE">
<h5>Note</h5>
<p>An <a class="xref" href="../../api/Bunit.IRenderedComponent-1.html">IRenderedComponent<TComponent></a> inherits from <a class="xref" href="../../api/Bunit.IRenderedComponent-1.html">IRenderedComponent<TComponent></a>. This page will only cover features of the <a class="xref" href="../../api/Bunit.IRenderedComponent-1.html">IRenderedComponent<TComponent></a> type. <a class="xref" href="../../api/Bunit.IRenderedComponent-1.html">IRenderedComponent<TComponent></a> is covered on the <a class="xref" href="verify-component-state.html">Verifying the state of a component under test</a> page.</p>
</div>
<h2 id="inspecting-dom-nodes">Inspecting DOM nodes</h2>
<p>The rendered markup from a component is available as a DOM node through the <a class="xref" href="../../api/Bunit.IRenderedComponent-1.html#Bunit_IRenderedComponent_1_Nodes">Nodes</a> property on <a class="xref" href="../../api/Bunit.IRenderedComponent-1.html">IRenderedComponent<TComponent></a>. The nodes and element types comes from <a href="https://anglesharp.github.io/">AngleSharp</a> that 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.</p>
<h3 id="finding-dom-elements">Finding DOM elements</h3>
<p>bUnit supports multiple different ways of searching and querying the rendered HTML elements:</p>
<ul>
<li><code>FindByLabelText(string labelText)</code> that takes a text string used to label an input element and returns an <code>IElement</code> as output, or throws an exception if none are found (this is included in the experimental library <a href="https://www.nuget.org/packages/bunit.web.query">bunit.web.query</a>). Use this method when possible compared to the generic <code>Find</code> and <code>FindAll</code> methods.</li>
<li><a class="xref" href="../../api/Bunit.RenderedComponentExtensions.html#Bunit_RenderedComponentExtensions_Find__1_Bunit_IRenderedComponent___0__System_String_"><code>Find(string cssSelector)</code></a> takes a "CSS selector" as input and returns an <code>IElement</code> as output, or throws an exception if none are found.</li>
<li><a class="xref" href="../../api/Bunit.RenderedComponentExtensions.html#Bunit_RenderedComponentExtensions_FindAll__1_Bunit_IRenderedComponent___0__System_String_"><code>FindAll(string cssSelector)</code></a> takes a "CSS selector" as input and returns a list of <code>IElement</code> elements.</li>
</ul>
<p>Let's see some examples of using the <a class="xref" href="../../api/Bunit.RenderedComponentExtensions.html#Bunit_RenderedComponentExtensions_Find__1_Bunit_IRenderedComponent___0__System_String_"><code>Find(string cssSelector)</code></a> and <a class="xref" href="../../api/Bunit.RenderedComponentExtensions.html#Bunit_RenderedComponentExtensions_FindAll__1_Bunit_IRenderedComponent___0__System_String_"><code>FindAll(string cssSelector)</code></a> methods to query the <code><FancyTable></code> component listed below.</p>
<pre><code class="lang-razor" name="FancyTable.razor"><table>
<caption>Lorem lipsum captium</caption>
<tbody>
<tr>
<td style="white-space:nowrap">Foo</td>
<td>Bar</td>
</tr>
<tr>
<td style="white-space:nowrap">Baz</td>
<td>Boo</td>
</tr>
</tbody>
</table>
</code></pre>
<p>To find the <code><caption></code> element and the first <code><td></code> elements in each row, do the following:</p>
<pre><code class="lang-csharp" highlight-lines="3-4">var tableCaption = cut.Find("caption");
var tableCells = cut.FindAll("td:first-child");
Assert.Empty(tableCaption.Attributes);
</code></pre>
<p>Once you have one or more elements, you verify against them, such as by inspecting their properties through the DOM API. For example:</p>
<pre><code class="lang-csharp"> Assert.All(tableCells, td => td.HasAttribute("style"));
}
}
</code></pre><h4 id="auto-refreshing-find-queries">Auto-refreshing Find() queries</h4>
<p>An element found with the <a class="xref" href="../../api/Bunit.RenderedComponentExtensions.html#Bunit_RenderedComponentExtensions_Find__1_Bunit_IRenderedComponent___0__System_String_"><code>Find(string cssSelector)</code></a> method will be updated if the component it came from is re-rendered.</p>
<p>However, that does not apply to elements that are found by traversing the DOM tree via the <a class="xref" href="../../api/Bunit.IRenderedComponent-1.html#Bunit_IRenderedComponent_1_Nodes">Nodes</a> property on <a class="xref" href="../../api/Bunit.IRenderedComponent-1.html">IRenderedComponent<TComponent></a>, 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.</p>
<p>As a result of this, it is always recommended to use the <a class="xref" href="../../api/Bunit.RenderedComponentExtensions.html#Bunit_RenderedComponentExtensions_Find__1_Bunit_IRenderedComponent___0__System_String_"><code>Find(string cssSelector)</code></a> method when searching for a single element. Alternatively, always reissue the query whenever you need the element.</p>
<h2 id="semantic-comparison-of-markup">Semantic comparison of markup</h2>
<p>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.</p>
<h3 id="how-does-the-semantic-html-comparer-work">How does the semantic HTML comparer work?</h3>
<p>The comparer takes two HTML fragments (e.g. in the form of a C# string) as input, and returns <code>true</code> if both HTML fragments result in the same visual rendered output in a web browser. If not, it returns <code>false</code>.</p>
<p>For example, a web browser will render this HTML:</p>
<pre><code class="lang-html"><span>Foo Bar</span>
</code></pre>
<p>This will be done in exactly the same way as this HTML:</p>
<pre><code class="lang-html"><span>
Foo Bar
</span>
</code></pre>
<p>This is why it makes sense to allow tests to pass, <em>even</em> when the rendered HTML markup is not entirely identical to the expected HTML from a normal string comparer's perspective.</p>
<p>bUnit's semantic HTML comparer safely ignores things like insignificant whitespace and the order of attributes on elements, as well as many more things. <strong>This leads to much more stable tests, as - for example - a reformatted component doesn't break its tests because of insignificant whitespace changes.</strong> More details of the semantic comparer can be found on the <a class="xref" href="semantic-html-comparison.html">Customizing the semantic HTML comparison</a> page.</p>
<h3 id="the-markupmatches-method">The MarkupMatches() method</h3>
<p>The HTML comparer can be easily accessed through <code>MarkupMatches()</code> extension methods, available in places that represent HTML fragments in bUnit, i.e. on <a class="xref" href="../../api/Bunit.IRenderedComponent-1.html">IRenderedComponent<TComponent></a> and the <code>INode</code> and <code>INodeList</code> types.</p>
<p>In the following examples, the <code><Heading></code> component listed below will be used as the component under test.</p>
<pre><code class="lang-razor" name="Heading.razor"><h3 id="heading-1337" required>
Heading text
<small class="text-muted mark">
Secondary text
</small>
</h3>
</code></pre>
<p>To use the <code>MarkupMatches()</code> method to perform a semantic comparison of the output of the <code><Heading></code> component through its <a class="xref" href="../../api/Bunit.IRenderedComponent-1.html">IRenderedComponent<TComponent></a>, do the following:</p>
<pre><code class="lang-csharp" highlight-lines="3-6">var cut = Render<Heading>();
cut.MarkupMatches(@"<h3 id=""heading-1337"" required>
Heading text
<small class=""mark text-muted"">Secondary text</small>
</h3>");
</code></pre>
<p>The highlighted line shows the call to the <code>MarkupMatches()</code> 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 <code><Heading></code> component. It even works when the CSS class list is not in the same order on the <code><small></code> element.</p>
<p>The <code>MarkupMatches()</code> method is also available on <code>INode</code> and <code>INodeList</code> types, for example:</p>
<pre><code class="lang-csharp" highlight-lines="3-4">var cut = Render<Heading>();
var smallElm = cut.Find("small");
smallElm.MarkupMatches(@"<small class=""mark text-muted"">Secondary text</small>");
</code></pre>
<p>Here we use the <code>Find(string cssSelector)</code> method to find the <code><small></code> element, and only verify it and its content and attributes.</p>
<div class="TIP">
<h5>Tip</h5>
<p>Working with <code>Find()</code>, <code>FindAll()</code>, <code>INode</code> and <code>INodeList</code> is covered later on this page.</p>
</div>
<p>Text content can also be verified with the <code>MarkupMatches()</code> method, e.g. the text inside the <code><small></code> 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 <code>Trim()</code> method isn't enough. For example:</p>
<pre><code class="lang-csharp" highlight-lines="3">var cut = Render<Heading>();
var smallElmText = cut.Find("small").TextContent;
smallElmText.MarkupMatches("Secondary text");
</code></pre>
<p>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.</p>
<p>Learn more about the customization options on the <a class="xref" href="semantic-html-comparison.html">Customizing the semantic HTML comparison</a> page.</p>
<h2 id="verification-of-raw-markup">Verification of raw markup</h2>
<p>To access the rendered markup of a component, just use the <a class="xref" href="../../api/Bunit.IRenderedComponent-1.html#Bunit_IRenderedComponent_1_Markup">Markup</a> property on <a class="xref" href="../../api/Bunit.IRenderedComponent-1.html">IRenderedComponent<TComponent></a>. This holds the <em>raw</em> HTML from the component as a <code>string</code>.</p>
<div class="WARNING">
<h5>Warning</h5>
<p>Be aware that all indentions and whitespace in your components (<code>.razor</code> 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 <code>Trim()</code> 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.</p>
<p>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.</p>
</div>
<p>To get the markup as a string, do the following:</p>
<pre><code class="lang-csharp" highlight-lines="3"> var renderedMarkup = cut.Markup;
Assert.Equal("<h1>Hello world from Blazor</h1>", renderedMarkup);
}
</code></pre>
<p>Standard string assertions can be performed against the markup string, such as checking whether it contains a value or is empty.</p>
</article>
<div class="contribution d-print-none">
<a href="https://github.com/bUnit-dev/bUnit/blob/stable/docs/site/docs/verification/verify-markup.md/#L1" class="edit-link">Edit this page</a>
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
<div class="border-top text-center pt-4">
<a class="d-block" href="https://dotnetfoundation.org" title=".NET Foundation">
<img width="150" src="/sponsors/dotnetfoundation_v4_purple.svg" alt=".NET Foundation">
</a>
<p class="pt-1"><small>Supported by the <a href="https://dotnetfoundation.org">.NET Foundation</a>.</small></p>
</div>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary bg-body-tertiary">
<div class="container-xxl">
<div class="flex-fill">
<small>Documentation updated on 2026-04-02T19:21:29.0000000+00:00 in commit 88c4a7382a</small>
</div>
</div>
</footer>
</body>
</html>