|
1 | 1 | --- |
2 | 2 | uid: mocking-ijsruntime |
3 | | -title: Mocking Blazor's IJsRuntime |
| 3 | +title: Mocking Blazor's IJSRuntime |
4 | 4 | --- |
5 | 5 |
|
6 | | -# Mocking Blazor's `IJsRuntime` |
| 6 | +# Mocking Blazor's `IJSRuntime` |
7 | 7 |
|
8 | | -This page is on the todo list. |
| 8 | +It is common for Blazor components to use `IJSRuntime` to call JavaScript, and since bUnit does not run JavaScript, mocking `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 | 9 |
|
10 | | -To see examples of how to mock the JsRuntime, go to the [C# test examples](/docs/csharp-test-examples.html#testing-components-that-use-on-ijsruntime) page. |
| 10 | +bUnit comes a tailor built mock of `IJSRuntime`, that allows you to specify how JavaScript interop calls should be handled, what values they should return, and to verify that they have happened. |
11 | 11 |
|
12 | | -- TODO: https://github.com/egil/bunit/issues/67 |
| 12 | +If you have more complex mocking needs, or 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. |
| 13 | + |
| 14 | +The following sections shows how to use the built-in mock of `IJSRuntime`. |
| 15 | + |
| 16 | +## Registering the mock `IJSRuntime` |
| 17 | + |
| 18 | +A mock of `IJSRuntime` must be added to the `Services` collection, just like other services that a component under test requires. This is done like this: |
| 19 | + |
| 20 | +```csharp |
| 21 | +using var ctx = new TestContext(); |
| 22 | +var mockJS = ctx.Services.AddMockJSRuntime(); |
| 23 | +``` |
| 24 | + |
| 25 | +Calling `AddMockJSRuntime()` returns a <xref:Bunit.TestDoubles.JSInterop.MockJSRuntimeInvokeHandler>, which is used to set up expected calls and verify invocations. |
| 26 | + |
| 27 | +### Strict vs loose mode |
| 28 | + |
| 29 | +The `AddMockJSRuntime()` method takes an optional <xref:Bunit.TestDoubles.JSInterop.JSRuntimeMockMode> parameter as input, which defaults to `Loose`, if not provided. |
| 30 | + |
| 31 | +- **Loose** mode configures the mock 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. |
| 32 | +- **Strict** mode configures the mock 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. |
| 33 | + |
| 34 | +To set the mock to strict mode, do the following: |
| 35 | + |
| 36 | +```csharp |
| 37 | +using var ctx = new TestContext(); |
| 38 | +var mockJS = ctx.Services.AddMockJSRuntime(JSRuntimeMockMode.Strict); |
| 39 | +``` |
| 40 | + |
| 41 | +## Setting up invocations |
| 42 | + |
| 43 | +Use the `Setup<TResult>(...)` and `SetupVoid(...)` methods to configure the mock to handle calls from the matching `InvokeAsync<TResult>(...)` and `InvokeVoidAsync(...)` methods on `IJSRuntime`. |
| 44 | + |
| 45 | +When an invocation is set up through 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. |
| 46 | + |
| 47 | +Here are two examples: |
| 48 | + |
| 49 | +```csharp |
| 50 | +using var ctx = new TestContext(); |
| 51 | +var mockJS = ctx.Services.AddMockJSRuntime(); |
| 52 | + |
| 53 | +// Set up an invocation and specify the result value immidiately |
| 54 | +mockJS.Setup<string>("getPageTitle").SetResult("bUnit is awesome"); |
| 55 | + |
| 56 | +// Set up an invocation without specifying the result |
| 57 | +var plannedInvocation = mockJS.SetupVoid("startAnimation"); |
| 58 | + |
| 59 | +// ... other test code |
| 60 | +
|
| 61 | +// Later in the test, mark the invocation as completed. |
| 62 | +// SetResult() is not used in this case since InvokeVoidAsync |
| 63 | +// only completes or throws, it doesnt return a value. |
| 64 | +// Any calls to InvokeVoidAsync(...) up till this point will |
| 65 | +// have received an incompleted Task which the component |
| 66 | +// is likely awaiting until the call to SetCompleted() below. |
| 67 | +plannedInvocation.SetCompleted(); |
| 68 | +``` |
| 69 | + |
| 70 | +## Verifying invocations |
| 71 | + |
| 72 | +All calls to the `InvokeAsync<TResult>(...)` and `InvokeVoidAsync(...)` methods on the mock are stored in its `Invocations` list, which can be inspected and asserted against. In addition to this, all planned invocations has their own `Invocations` list, which only contains their invocations. |
| 73 | + |
| 74 | +Invocations are represented by the `JSRuntimeInvocation` type, which has three properties of interest when verifying an invocation happened as expected: |
| 75 | + |
| 76 | +- `Identifier` - the name of the function name/identifier passed to the invoke method. |
| 77 | +- `Arguments` - a list of arguments passed to the invoke method. |
| 78 | +- `CancellationToken` - the cancellation token passed to the invoke method (if any). |
0 commit comments