-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathtrigger-renders.html
More file actions
257 lines (221 loc) · 15.6 KB
/
Copy pathtrigger-renders.html
File metadata and controls
257 lines (221 loc) · 15.6 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
252
253
254
255
256
257
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Triggering a render life cycle on a component | bUnit </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Triggering a render life cycle on 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/interaction/trigger-renders.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="trigger-renders">
<h1 id="triggering-a-render-life-cycle-on-a-component">Triggering a render life cycle on a component</h1>
<p>To trigger a re-render of a component under test, a reference to it through a <a class="xref" href="../../api/Bunit.IRenderedComponent-1.html">IRenderedComponent<TComponent></a> type is needed. When using the <a class="xref" href="../../api/Bunit.BunitContext.html">BunitContext</a>'s <code>Render<TComponent>()</code> method, this is the type returned.</p>
<p>In <code>.razor</code> based tests, using the <a class="xref" href="../../api/Bunit.BunitContext.html">BunitContext</a>'s <a class="xref" href="../../api/Bunit.BunitContext.html#Bunit_BunitContext_Render_Microsoft_AspNetCore_Components_RenderFragment_">Render(RenderFragment)</a> method also returns an <a class="xref" href="../../api/Bunit.IRenderedComponent-1.html">IRenderedComponent<TComponent></a> (as opposed to the <a class="xref" href="../../api/Bunit.BunitContext.html#Bunit_BunitContext_Render_Microsoft_AspNetCore_Components_RenderFragment_">Render(RenderFragment)</a> method which returns the more simple <a class="xref" href="../../api/Bunit.IRenderedComponent-1.html">IRenderedComponent<TComponent></a>).</p>
<p>If you have a <a class="xref" href="../../api/Bunit.IRenderedComponent-1.html">IRenderedComponent<TComponent></a> in a test, but need a child component's <a class="xref" href="../../api/Bunit.IRenderedComponent-1.html">IRenderedComponent<TComponent></a>, then use the <code>FindComponent<TComponent>()</code> or the <code>FindComponents<TComponent>()</code> methods, which traverse down the render tree and finds rendered components.</p>
<p>With a <a class="xref" href="../../api/Bunit.IRenderedComponent-1.html">IRenderedComponent<TComponent></a>, it is possible to cause the component to render again directly through the <a class="xref" href="../../api/Bunit.BunitContext.html#Bunit_BunitContext_Render__1_Microsoft_AspNetCore_Components_RenderFragment_"><code>Render()</code></a> method or one of the <a class="xref" href="../../api/Bunit.RenderedComponentRenderExtensions.html#Bunit_RenderedComponentRenderExtensions_Render__1_Bunit_IRenderedComponent___0__System_Action_Bunit_ComponentParameterCollectionBuilder___0___"><code>Render()</code></a> methods, or indirectly through the <a class="xref" href="../../api/Bunit.RenderedComponentInvokeAsyncExtensions.html#Bunit_RenderedComponentInvokeAsyncExtensions_InvokeAsync__1_Bunit_IRenderedComponent___0__System_Action_"><code>InvokeAsync()</code></a> method.</p>
<p>Let's look at how to use each of these methods to cause a re-render.</p>
<h2 id="render">Render</h2>
<p>The <a class="xref" href="../../api/Bunit.RenderedComponentRenderExtensions.html#Bunit_RenderedComponentRenderExtensions_Render__1_Bunit_IRenderedComponent___0__System_Action_Bunit_ComponentParameterCollectionBuilder___0___"><code>Render()</code></a> method tells the renderer to re-render the component, i.e. go through its life-cycle methods (except for <code>OnInitialized()</code> and <code>OnInitializedAsync()</code> methods). To use it, do the following:</p>
<pre><code class="lang-csharp" highlight-lines="5">var cut = Render<Heading>();
Assert.Equal(1, cut.RenderCount);
// Re-render without new parameters
cut.Render();
Assert.Equal(2, cut.RenderCount);
</code></pre>
<p>The highlighted line shows the call to <a class="xref" href="../../api/Bunit.RenderedComponentRenderExtensions.html#Bunit_RenderedComponentRenderExtensions_Render__1_Bunit_IRenderedComponent___0__System_Action_Bunit_ComponentParameterCollectionBuilder___0___"><code>Render()</code></a>.</p>
<div class="TIP">
<h5>Tip</h5>
<p>The number of renders a component has been through can be inspected and verified using the <a class="xref" href="../../api/Bunit.IRenderedComponent-1.html#Bunit_IRenderedComponent_1_RenderCount">RenderCount</a> property.</p>
</div>
<h2 id="render-1">Render</h2>
<p>The <a class="xref" href="../../api/Bunit.RenderedComponentRenderExtensions.html#Bunit_RenderedComponentRenderExtensions_Render__1_Bunit_IRenderedComponent___0__System_Action_Bunit_ComponentParameterCollectionBuilder___0___"><code>Render(...)</code></a> methods tells the renderer to re-render the component with new parameters, i.e. go through its life-cycle methods (except for <code>OnInitialized()</code> and <code>OnInitializedAsync()</code> methods), passing the new parameters — <em>but only the new parameters</em> — to the <code>SetParametersAsync()</code> method. To use it, do the following:</p>
<pre><code class="lang-csharp" highlight-lines="7-9">var cut = Render<Item>(parameters => parameters
.Add(p => p.Value, "Foo")
);
cut.MarkupMatches("<span>Foo</span>");
// Re-render with new parameters
cut.Render(parameters => parameters
.Add(p => p.Value, "Bar")
);
cut.MarkupMatches("<span>Bar</span>");
</code></pre>
<p>The highlighted line shows the call to <a class="xref" href="../../api/Bunit.RenderedComponentRenderExtensions.html#Bunit_RenderedComponentRenderExtensions_Render__1_Bunit_IRenderedComponent___0__System_Action_Bunit_ComponentParameterCollectionBuilder___0___"><code>Render()</code></a>, which is also available as a version that takes the zero or more component parameters, e.g. created through the component parameter factory helper methods, if you prefer that method of passing parameters.</p>
<div class="NOTE">
<h5>Note</h5>
<p>Passing parameters to components through the <a class="xref" href="../../api/Bunit.RenderedComponentRenderExtensions.html#Bunit_RenderedComponentRenderExtensions_Render__1_Bunit_IRenderedComponent___0__System_Action_Bunit_ComponentParameterCollectionBuilder___0___"><code>Render(...)</code></a> methods is identical to doing it with the <code>Render<TComponent>(...)</code> methods, described in detail on the <a class="xref" href="../providing-input/passing-parameters-to-components.html">Passing parameters to components</a> page.</p>
</div>
<h2 id="invokeasync">InvokeAsync</h2>
<p>Invoking methods on a component under test, which causes a render, e.g. by calling <code>StateHasChanged</code>, can result in the following error, if the caller is running on another thread than the renderer's thread:</p>
<blockquote>
<p>The current thread is not associated with the Dispatcher. Use <code>InvokeAsync()</code> to switch execution to the Dispatcher when triggering rendering or component state.</p>
</blockquote>
<p>If you receive this error, you need to invoke your method inside an <code>Action</code> delegate passed to the <code>InvokeAsync()</code> method.</p>
<p>Let’s look at an example of this, using the <code><Calc></code> component listed below:</p>
<pre><code class="lang-cshtml" name="Calc.razor"><output>@result</output>
@code
{
int result = 0;
public void Calculate(int x, int y)
{
result = x + y;
StateHasChanged();
}
}
</code></pre>
<p>To invoke the <code>Calculate()</code> method on the component instance, do the following:</p>
<pre><code class="lang-csharp" highlight-lines="5">var cut = Render<Calc>();
// Indirectly re-renders through the call to StateHasChanged
// in the Calculate(x, y) method.
cut.InvokeAsync(() => cut.Instance.Calculate(1, 2));
cut.MarkupMatches("<output>3</output>");
</code></pre>
<p>The highlighted line shows the call to <code>InvokeAsync()</code>, which is passed an <code>Action</code> delegate that calls the <code>Calculate</code> method.</p>
<div class="TIP">
<h5>Tip</h5>
<p>The instance of a component under test is available through the <a class="xref" href="../../api/Bunit.IRenderedComponent-1.html#Bunit_IRenderedComponent_1_Instance">Instance</a> property.</p>
</div>
<h3 id="advanced-use-cases">Advanced use cases</h3>
<p>In some scenarios, the method being invoked may also return a value, as demonstrated in the following example.</p>
<pre><code class="lang-cshtml" name="CalcWithReturnValue.razor"><output>@result</output>
@code
{
int result = 0;
public int Calculate(int x, int y)
{
result = x + y;
StateHasChanged();
return result;
}
}
</code></pre>
<p>Testing this scenario follows the same procedure as before, with the addition of using the return value from <code>InvokeAsync()</code>:</p>
<pre><code class="lang-csharp" highlight-lines="4">var cut = Render<CalcWithReturnValue>();
// Indirectly re-renders and returns a value.
var result = await cut.InvokeAsync(() => cut.Instance.Calculate(1, 2));
Assert.Equal(3, result);
cut.MarkupMatches("<output>3</output>");
</code></pre>
<p>This can also be used to assert intermediate states during an asynchronous operation, like the example below:</p>
<pre><code class="lang-cshtml" name="CalcWithLoading.razor"><output>@(result is null ? "Loading" : result.ToString())</output>
@code
{
int? result = 0;
public async Task Calculate(int x, int y)
{
result = null;
StateHasChanged();
// Simulate an asynchronous operation, like fetching data.
await Task.Delay(500);
result = x + y;
StateHasChanged();
}
}
</code></pre><pre><code class="lang-csharp" highlight-lines="7">// Arrange - renders the CalcWithLoading component
var cut = Render<CalcWithLoading>();
// Indirectly re-renders and returns the task returned by Calculate().
// The explicit <Task> here is important, otherwise the call to Calculate()
// will be awaited automatically.
var task = await cut.InvokeAsync<CalcWithLoading, Task>(() => cut.Instance.Calculate(1, 2));
cut.MarkupMatches("<output>Loading</output>");
// Wait for the task to complete.
await task;
cut.WaitForAssertion(() => cut.MarkupMatches("<output>3</output>"));
</code></pre>
</article>
<div class="contribution d-print-none">
<a href="https://github.com/bUnit-dev/bUnit/blob/stable/docs/site/docs/interaction/trigger-renders.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-08-10T12:54:03.0000000+00:00 in commit d4d81d9e5b</small>
</div>
</div>
</footer>
</body>
</html>