Skip to content

Commit 5734de7

Browse files
committed
Updated bUnits IJSRuntime docs
1 parent b564a6a commit 5734de7

File tree

7 files changed

+173
-101
lines changed

7 files changed

+173
-101
lines changed

docs/samples/components/UserRights.razor

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
</AuthorizeView>
88
<ul>
99
<AuthorizeView>
10-
@foreach (var claim in @context.User.FindAll(x => x.Type != ClaimTypes.Name))
10+
@foreach (var claim in @context.User.FindAll(x => x.Type != ClaimTypes.Name && x.Type != ClaimTypes.Role))
1111
{
1212
<li>@GetClaimName(claim): @claim.Value</li>
1313
}
Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,25 @@
11
<Project Sdk="Microsoft.NET.Sdk.Razor">
22

3-
<PropertyGroup>
3+
<PropertyGroup>
44
<TargetFrameworks>netstandard2.0;net5.0</TargetFrameworks>
5-
<RazorLangVersion>3.0</RazorLangVersion>
6-
<RootNamespace>Bunit.Docs.Samples</RootNamespace>
7-
</PropertyGroup>
5+
<RazorLangVersion>3.0</RazorLangVersion>
6+
<RootNamespace>Bunit.Docs.Samples</RootNamespace>
7+
</PropertyGroup>
88

99
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
1010
<PackageReference Include="Microsoft.Extensions.Logging" Version="3.1" />
1111
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="3.1" />
1212
<PackageReference Include="Microsoft.AspNetCore.Components" Version="3.1" />
1313
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="3.1" />
14-
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="3.1" />
14+
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="3.1" />
1515
</ItemGroup>
1616

1717
<ItemGroup Condition="'$(TargetFramework)' == 'net5.0'">
1818
<PackageReference Include="Microsoft.Extensions.Logging" Version="5.0.0" />
1919
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="5.0.0" />
2020
<PackageReference Include="Microsoft.AspNetCore.Components" Version="5.0.0" />
2121
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="5.0.0" />
22-
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="5.0.0" />
22+
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="5.0.0" />
2323
</ItemGroup>
2424

2525
</Project>

docs/site/docs/getting-started/fixture-details.md

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,7 @@ In the example above, the setup and test methods are declared in a `@code { }` b
3131

3232
You can have the methods anywhere inside the test component you want, which can be useful. For example, if you have the same setup steps for multiple tests, they can be placed in a common setup method that the tests in the same test component can share, avoiding code duplication.
3333

34-
The methods can also be declared in the parameter directly, e.g.: `<Fixture Setup="f => f.Services.AddMockJsRuntime()" ...>`, to save space and when they are very short.
35-
36-
> [!TIP]
37-
> Learn more about mocking and `AddMockJsRuntime()` on the <xref:mocking-ijsruntime> page.
34+
TODO EGIL
3835

3936
**Other parameters**
4037

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
---
2+
uid: emulating-ijsruntime
3+
title: Emulating Blazor's IJSRuntime
4+
---
5+
6+
# Emulating Blazor's `IJSRuntime`
7+
8+
It is common for Blazor components to use `IJSRuntime` to call JavaScript, and since bUnit does not run JavaScript, emulating `IJSRuntime` is needed for components that uses it. In that regard, `IJSRuntime` is no different than other services that a component might depend on.
9+
10+
bUnit comes with it's own JSInterop, a tailor built implementation of `IJSRuntime` that is _active by default_, allowing you to specify how JavaScript interop calls should be handled, what values they calls should return, and also allowing you to verify that they the calls have happened. The implementation is running in "strict mode", which means means it will throw an exception if it receives an invocation it has not been configured to handle. See more about strict vs loose mode in the following section.
11+
12+
If you prefer to use the same mocking framework for all mocking in your tests to keep things consistent, general purpose mocking frameworks like [Moq](https://github.com/Moq), [JustMock Lite](https://github.com/telerik/JustMockLite), or [NSubstitute](https://nsubstitute.github.io/) all works nicely with bUnit and can be used to mock `IJSRuntime`. In general, registering an implementation of `IJSRuntime` with bUnit's `Services` collection replaces bUnit's implementation.
13+
14+
The following sections shows how to use the built-in implementation of `IJSRuntime`.
15+
16+
> [!NOTE]
17+
> In the beta versions of bUnit, you had to explicitly add the mock JSRuntime by calling `Services.AddMockJSRuntime()`. That is no longer needed, and doesn't work any more.
18+
19+
## Strict vs loose mode
20+
21+
bUnit's JSInterop can run in two modes, **strict** or **loose**:
22+
23+
- **Loose** mode configures the implementation to just return the default value when it receives an invocation that has not been explicitly set up, e.g. if a component calls `InvokeAsync<int>(...)` the mock will simply return `default(int)` back to it immediately.
24+
- **Strict** mode configures the implementation to throw an exception if it is invoked with a method call it has _not_ been set up to handle explicitly. This is useful if you want to ensure that a component only performs a specific set of `IJSRuntime` invocations.
25+
26+
By default, the bUnit's JSInterop runs in **Strict** mode. To change the mode, do the following:
27+
28+
```csharp
29+
using var ctx = new TestContext();
30+
ctx.JSInterop.Mode = JSRuntimeMockMode.Loose;
31+
```
32+
33+
## Setting up invocations
34+
35+
Use the `Setup<TResult>(...)` and `SetupVoid(...)` methods to configure the implementation to handle calls from the **matching** `InvokeAsync<TResult>(...)` and `InvokeVoidAsync(...)` methods on `IJSRuntime`.
36+
37+
Use the parameterless `Setup<TResult>()` method to emulate any call to `InvokeAsync<TResult>(...)` with a given return type `TResult` and use the parameterless `SetupVoid()` to emulate any call to `InvokeVoidAsync(...)`.
38+
39+
When an invocation is set up through of the `Setup<TResult>(...)` and `SetupVoid(...)` methods, a `JSRuntimePlannedInvocation<TResult>` object is returned. This can be used to set a result or an exception, to emulate what can happen during a JavaScript interop call in Blazor.
40+
41+
Similarly when the parameterless `Setup<TResult>()` and `SetupVoid()` methods are used a `JSRuntimeCatchAllPlannedInvocation<TResult>` object is returned which can be used to set the result of invocation.
42+
43+
Here are two examples:
44+
45+
```csharp
46+
using var ctx = new TestContext();
47+
48+
// Set up an invocation and specify the result value immediately
49+
ctx.JSInterop.Setup<string>("getPageTitle").SetResult("bUnit is awesome");
50+
51+
// Set up an invocation without specifying the result
52+
var plannedInvocation = ctx.JSInterop.SetupVoid("startAnimation");
53+
54+
// ... other test code
55+
56+
// Later in the test, mark the invocation as completed.
57+
// SetResult() is not used in this case since InvokeVoidAsync
58+
// only completes or throws, it doesn’t return a value.
59+
// Any calls to InvokeVoidAsync(...) up till this point will
60+
// have received an incompleted Task which the component
61+
// is likely waiting until the call to SetCompleted() below.
62+
plannedInvocation.SetCompleted();
63+
```
64+
65+
## Verifying invocations
66+
67+
All calls to the `InvokeAsync<TResult>(...)` and `InvokeVoidAsync(...)` methods in bUnit's JSInterop are stored in its `Invocations` list, which can be inspected and asserted against. In addition to this, all planned invocations have their own `Invocations` list which only contain their invocations.
68+
69+
Invocations are represented by the `JSRuntimeInvocation` type which has three properties of interest when verifying an invocation happened as expected:
70+
71+
- `Identifier` - the name of the function name/identifier passed to the invoke method.
72+
- `Arguments` - a list of arguments passed to the invoke method.
73+
- `CancellationToken` - the cancellation token passed to the invoke method (if any).
74+
75+
To verify these, just use the assertion methods you normally use.
76+
77+
### Support for `IJSInProcessRuntime` and `IJSUnmarshalledRuntime`
78+
79+
bUnit's `IJSRuntime` supports being cast to the `IJSInProcessRuntime` and `IJSUnmarshalledRuntime` types, just like Blazors `IJSRuntime`.
80+
81+
To set up a handler for a `Invoke` and `InvokeUnmarshalled` call, just use the regular `Setup` and `SetupVoid` methods on bUnit's JSInterop.
82+
83+
## Support for importing JavaScript Modules
84+
85+
Since the .NET 5 release of Blazor, it has been possible to import JavaScript modules directly from components. This is supported by bUnit's JSInterop through the `SetupModule` methods, that setup calls to `InvokeAsync<IJSObjectReference>`.
86+
87+
The `SetupModule` methods return a module JSInterop, that can be configured to handle the any JavaScript calls using the `Setup` and `SetupVoid` methods. For example, to configrue bUnit's JSInterop to handle an import of the JavaScript module `hello.js`, and a call to the function `world()` in that model, do the following:
88+
89+
```csharp
90+
using var ctx = new TestContext();
91+
92+
var moduleInterop = ctx.JSInterop.SetupModule("hello.js");
93+
moduleInterop.SetupVoid("world");
94+
```
95+
96+
### Module Interop Mode
97+
98+
By default, a module Interop inherits the `Mode` setting from the root JSInterop in bUnit. However, you can override it explictly and have it in a different mode from other module Interop or the root JSInterop. Just set the `Mode` property, e.g.:
99+
100+
```csharp
101+
var moduleInterop = ctx.JSInterop.SetupModule("hello.js");
102+
moduleInterop.Mode = JSRuntimeMockMode.Loose;
103+
```
104+
105+
### Support for `IJSInProcessObjectReference` and `IJSUnmarshalledObjectReference`
106+
107+
bUnit's `IJSObjectReference` supports being cast to the `IJSInProcessObjectReference` and `IJSUnmarshalledObjectReference` types, just like Blazors `IJSObjectReference`.
108+
109+
To set up a handler for a `Invoke` and `InvokeUnmarshalled` call, just use the regular `Setup` and `SetupVoid` methods on bUnit's JSInterop.
110+
111+
## First Party JSInterop Component Emulation
112+
113+
Blazor comes out of the box with a few components that requires a working JSInterop. bUnit's JSInterop is setup to emulate the JavaScript interactions of those components. The following sections describes how the interaction is emulated for the supported components.
114+
115+
### <Virtualize> JSInterop Emulation
116+
117+
The `<Virtualize>` component require JavaScript to notify it about the available screen space it is being rendered to, and when the users scrolls the viewport, to trigger the loading of new data. bUnit emulates this interaction by telling the `<Virtualize>` component that the viewport is `1,000,000,000` pixels large. That should ensure that all items is loaded, which makes sense in a testing scenario.
118+
119+
To test the `<Placeholder>` template of the `<Virtualize>` component, create a items provider that doesn't return all items when queried.
120+
121+
### FocusAsync JSInterop Emulation
122+
123+
Support for the [`FocusAsync`](https://docs.microsoft.com/en-us/aspnet/core/blazor/components/event-handling?view=aspnetcore-5.0#focus-an-element) method on `ElementReference` in Blazor's .NET 5 release works by simply registering the invocations, which can then be verified to have happened.
124+
125+
To verify that the `FocusAsync` has been called in the `<ClickToFocus>` component:
126+
127+
```cshtml
128+
<input @ref="exampleInput" />
129+
130+
<button @onclick="ChangeFocus">Focus the Input Element</button>
131+
132+
@code {
133+
private ElementReference exampleInput;
134+
private async Task ChangeFocus()
135+
{
136+
await exampleInput.FocusAsync();
137+
}
138+
}
139+
```
140+
141+
Do the following:
142+
143+
```csharp
144+
using var ctx = new TestContext();
145+
var cut = RenderComponent<ClickToFocus>();
146+
var inputElement = cut.Find("input");
147+
148+
cut.Find("button").Click(); // Triggers onclick handler that sets focus of input element
149+
150+
ctx.JSInterop.VerifyFocusAsyncInvoke() // Verifies that a FocusAsync call has happenend
151+
.Arguments[0] // gets the first argument passed to the FocusAsync method
152+
.ShouldBeElementReferenceTo(inputElement); // verify that it is an element reference to the input element.
153+
```
154+
155+
## Support for `IJSInProcessRuntime` and `IJSUnmarshalledRuntime`
156+
157+
bUnit's `IJSRuntime` supports being cast to the `IJSInProcessRuntime` and `IJSUnmarshalledRuntime` types, just like Blazors `IJSRuntime`.
158+
159+
To set up a handler for a `Invoke` and `InvokeUnmarshalled` call, just use the regular `Setup` and `SetupVoid` methods on bUnit's JSInterop.

docs/site/docs/test-doubles/index.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ uid: test-doubles
33
title: Mocking and Faking Component Dependencies
44
---
55

6-
# Mocking Component Dependencies
6+
# Mocking or Faking Component Dependencies
77

8-
Mocking a component under tests dependencies (services) can be the difference between being able to write a stable test that is easy to understand, and the opposite. bUnit does not have any particular preferences when it comes to mocking frameworks; all the usual suspects will work with bUnit. For example, Moq, JustMock and NSubstitute all work well with bUnit, so pick the one you are the most comfortable with and use it.
8+
Mocking or faking a component under tests dependencies (services) can be the difference between being able to write a stable test that is easy to understand, and the opposite. bUnit does not have any particular preferences when it comes to mocking frameworks; all the usual suspects will work with bUnit. For example, Moq, JustMock and NSubstitute all work well with bUnit, so pick the one you are the most comfortable with and use it.
99

10-
bUnit does, however, come with a few specially crafted mock helpers for some of Blazor’s built-in services. These are designed to make it easy and clean to mock them. More are planned for the future too.
10+
bUnit does, however, come with a few specially crafted test doubles for some of Blazor’s built-in services. These are designed to make it easy write tests that of components that uses these services. More are planned for the future too.
1111

12-
The built-in mock helpers are described on the following pages:
12+
The built-in test doubles are described on the following pages:
1313

1414
- <xref:faking-auth>
15-
- <xref:mocking-ijsruntime>
15+
- <xref:emulating-ijsruntime>

docs/site/docs/test-doubles/mocking-ijsruntime.md

Lines changed: 0 additions & 84 deletions
This file was deleted.

docs/site/docs/toc.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
# [Test Doubles](xref:test-doubles)
2626
## [Faking Authorization](xref:faking-auth)
2727
## [Mocking HttpClient](xref:mocking-httpclient)
28-
## [Mocking IJSRuntime](xref:mocking-ijsruntime)
28+
## [Mocking IJSRuntime](xref:emulating-ijsruntime)
2929

3030
# [Miscellaneous testing tips](xref:misc-test-tips)
3131
# [External Resources](xref:external-resources)

0 commit comments

Comments
 (0)