|
| 1 | +# C# based testing |
| 2 | + |
| 3 | +This pages documents how to do Blazor/Razor component testing using just C#. |
| 4 | + |
| 5 | +Before you get started, make sure you have read the [Getting started](/docs/Getting-Started.html) page and in particular the [Basics of Blazor component testing](/docs/Basics-of-Blazor-component-testing.html) section. It wont take long, and it will ensure you get a good start at component testing. |
| 6 | + |
| 7 | +> **NOTE:** You are currently required to write your tests using the xUnit framework. If popular demand requires it, this library can be made test framework independent in the future. |
| 8 | +
|
| 9 | +> **TIP:** Working with and asserting against the rendered component and its output is covered on the [Working with rendered components and fragments](/docs/Working-with-rendered-components-and-fragments.html) page. |
| 10 | +
|
| 11 | +**Content:** |
| 12 | + |
| 13 | +- [Creating an new test class](#creating-an-new-test-class) |
| 14 | +- [Executing test cases](#executing-test-cases) |
| 15 | +- [Rendering components during tests](#rendering-components-during-tests) |
| 16 | +- [Passing parameters and services to components during render](#passing-parameters-to-components-during-render) |
| 17 | +- [Registering and injecting services into components during render](#registering-and-injecting-services-into-components-during-render) |
| 18 | + |
| 19 | +**Further reading:** |
| 20 | + |
| 21 | +- [Working with rendered components and fragments](/docs/Working-with-rendered-components-and-fragments.html) |
| 22 | +- [Semantic HTML markup comparison](/docs/Semantic-HTML-markup-comparison.html) |
| 23 | +- [Mocking JsRuntime](/docs/Mocking-JsRuntime.html) |
| 24 | +- [C# test examples](/docs/CSharp-test-examples.html) |
| 25 | + |
| 26 | +## Creating an new test class |
| 27 | + |
| 28 | +All test classes are expected to inherit from `ComponentTestFixture`, which implements the `ITestContext` interface. The example below includes the needed using statements as well: |
| 29 | + |
| 30 | +```csharp |
| 31 | +using System; |
| 32 | +using Bunit; |
| 33 | +using Bunit.Mocking.JSInterop; |
| 34 | +using Microsoft.Extensions.DependencyInjection; |
| 35 | +using Xunit; |
| 36 | + |
| 37 | +public class MyComponentTest : ComponentTestFixture |
| 38 | +{ |
| 39 | + [Fact] |
| 40 | + public void MyFirstTest() |
| 41 | + { |
| 42 | + // ... |
| 43 | + } |
| 44 | +} |
| 45 | +``` |
| 46 | + |
| 47 | +The `ComponentTestFixture` contains all the logic for rendering components and correctly dispose of renderers, components, and HTML parsers after each test. |
| 48 | + |
| 49 | +## Executing test cases |
| 50 | + |
| 51 | +Since Blazor component tests are just regular xUnit test/facts, you execute them in exactly the same way as you would normal tests, i.e. by running `dotnet test` from the console or running the tests through the Test Explorer in Visual Studio. |
| 52 | + |
| 53 | +## Rendering components during tests |
| 54 | + |
| 55 | +To render a component, we use the `RenderComponent<TComponent>(params ComponentParameter[] parameters)` method. It will take the component (`TComponent`) through its usual life-cycle from `OnInitialized` to `OnAfterRender`. For example: |
| 56 | + |
| 57 | +```csharp |
| 58 | +public class ComponentTest : ComponentTestFixture // implements the ITestContext interface |
| 59 | +{ |
| 60 | + [Fact] |
| 61 | + public void Test1() |
| 62 | + { |
| 63 | + // Renders a MyComponent component and assigns the result to |
| 64 | + // a cut variable. CUT is short for Component Under Test. |
| 65 | + IRenderedComponent<MyComponent> cut = RenderComponent<MyComponent>(); |
| 66 | + } |
| 67 | +} |
| 68 | +``` |
| 69 | + |
| 70 | +The `RenderComponent<TComponent>(params ComponentParameter[] parameters) : IRenderedComponent<MyComponent>` method has these parts: |
| 71 | + |
| 72 | +- `TComponent` is the type of component you want to render. |
| 73 | +- `ComponentParameter[] parameters` represents parameters that will be passed to the component during render. |
| 74 | +- `IRenderedComponent<TComponent>` is the representation of the rendered component. Working with the rendered component and its output is covered on the [Working with rendered components and fragments](/docs/Working-with-rendered-components-and-fragments.html) page. |
| 75 | + |
| 76 | +### Passing parameters to components during render |
| 77 | + |
| 78 | +There are four types of parameters you can pass to a component being rendered through the `RenderComponent()` method: |
| 79 | + |
| 80 | +- Cascading values (normally provided by the `<CascadingValue>` component in `.razor` files). |
| 81 | +- Event callbacks (of type `EventCallback<T>` or `EventCallback`). |
| 82 | +- Child content, render fragments, or templates (of type `RenderFragment` and `RenderFragment<T>`). |
| 83 | +- All other normal parameters, including unmatched parameters. |
| 84 | + |
| 85 | +In addition to parameters, services can also be registered in the `ITestContext` and injected during component render. |
| 86 | + |
| 87 | +To show how, let us look at a few examples that correctly pass parameters and services to the following `AllTypesOfParams<TItem>` component: |
| 88 | + |
| 89 | +```cshtml |
| 90 | +@typeparam TItem |
| 91 | +@inject IJSRuntime jsRuntime |
| 92 | +@code { |
| 93 | + [Parameter(CaptureUnmatchedValues = true)] |
| 94 | + public IReadOnlyDictionary<string, object> Attributes { get; set; } |
| 95 | +
|
| 96 | + [Parameter] |
| 97 | + public string RegularParam { get; set; } |
| 98 | +
|
| 99 | + [CascadingParameter] |
| 100 | + public int UnnamedCascadingValue { get; set; } |
| 101 | +
|
| 102 | + [CascadingParameter(Name = "Named")] |
| 103 | + public int NamedCascadingValue { get; set; } |
| 104 | +
|
| 105 | + [Parameter] |
| 106 | + public EventCallback NonGenericCallback { get; set; } |
| 107 | +
|
| 108 | + [Parameter] |
| 109 | + public EventCallback<EventArgs> GenericCallback { get; set; } |
| 110 | +
|
| 111 | + [Parameter] |
| 112 | + public RenderFragment ChildContent { get; set; } |
| 113 | +
|
| 114 | + [Parameter] |
| 115 | + public RenderFragment OtherContent { get; set; } |
| 116 | +
|
| 117 | + [Parameter] |
| 118 | + public RenderFragment<TItem> ItemTemplate { get; set; } |
| 119 | +} |
| 120 | +``` |
| 121 | + |
| 122 | +And to render the `AllTypesOfParams<TItem>` component with all possible parameters set, use the following code: |
| 123 | + |
| 124 | +```csharp |
| 125 | +var cut = RenderComponent<AllTypesOfParams<string>>( |
| 126 | + // pass name-value attribute to be captured by the Attributes parameter |
| 127 | + ("some-unmatched-attribute", "unmatched value"), |
| 128 | + // pass value to the RegularParam parameter |
| 129 | + ("RegularParam", "some value"), |
| 130 | + // pass value to the UnnamedCascadingValue cascading parameter |
| 131 | + CascadingValue(42), |
| 132 | + // pass value to the NamedCascadingValue cascading parameter |
| 133 | + CascadingValue("Named", 1337), |
| 134 | + // pass action callback to the NonGenericCallback parameter |
| 135 | + EventCallback("NonGenericCallback", () => { /* logic here */ }), |
| 136 | + // pass action callback to the GenericCallback parameter |
| 137 | + EventCallback("GenericCallback", (EventArgs args) => { /* logic here */ }), |
| 138 | + // pass render fragment to the ChildContent parameter |
| 139 | + ChildContent("<h1>hello world</h1>"), |
| 140 | + // paas render fragment to the OtherContent parameter |
| 141 | + RenderFragment("OtherContent", "<h1>hello world</h1>"), |
| 142 | + // pass an template render fragment to the ItemTemplate parameter |
| 143 | + Template<string>("ItemTemplate", (item) => (builder) => { }) |
| 144 | +); |
| 145 | +``` |
| 146 | + |
| 147 | +- **Regular parameters** can easily be passed as `(string name, object? value)` pairs (they are automatically converted to a ComponentParameter). We see two examples of that with the _"RegularParam"_ and the unmatched attribute _"some-unmatched-attribute"_. |
| 148 | +- **Cascading values** can be passed both as named and unnamed via the `CascadingValue` helper method, as we see in the example above with _"UnnamedCascadingValue"_ and _"NamedCascadingValue"_. |
| 149 | +- **Event callbacks** can be passed as `Func` and `Action` types with and without input and return types, using the `EventCallback` helper method. The example above shows two examples in _"NonGenericCallback"_ and _"GenericCallback"_ |
| 150 | +- **Child content** and general **Render fragments** is passed to a component using the `ChildContent` or `RenderFragment` helper methods. The `ChildContent` and `RenderFragment` methods has two overloads, one that takes a (markup) string and a generic version, e.g. for child content, `ChildContent<TComponent>(params ComponentParameter[] parameters)`, which will generate the necessary render fragment to render a component as the child content. Note that the methods takes the same input arguments as the `RenderComponent` method, which means it too can be passed all the types of parameters shown in the example above. |
| 151 | +- **Templates** render fragments can be passed via the `Template<TValue>` method, which takes the name of the parameter and a `RenderFragment<TValue>` as input. Unfortunately, you will have to turn to the `RenderTreeBuilder` API to create templates at the moment. |
| 152 | + |
| 153 | +_**TIP:**_ Use the `nameof(Component.Parameter)` method to get parameter names in a refactor-safe way. For example, if we have a component `MyComponent` with a parameter named `RegularParam`, then use this when rendering: |
| 154 | + |
| 155 | +```csharp |
| 156 | +var cut = RenderComponent<MyComponent>( |
| 157 | + (nameof(MyComponent.RegularParam), "some value") |
| 158 | +); |
| 159 | +``` |
| 160 | + |
| 161 | +### Registering and injecting services into components during render |
| 162 | + |
| 163 | +When testing components that require services to be injected into them, i.e. `@inject IJsRuntime jsRuntime`, you must register the services or a mock thereof before you render your component. |
| 164 | + |
| 165 | +This is done via the `ITestContext.Services` property. Once a component has been rendered, no more services can be added to the service collection. |
| 166 | + |
| 167 | +If for example we want to render the with a dependency on an `IMyService`, we first have to call one of the `AddSingleton` methods on the service collection and register it. All the normal `AddSingleton` `ServiceCollection` overloads are available. |
| 168 | + |
| 169 | +In the case if a `IJsRuntime` dependency, we can however use the built-in [Mocking JsRuntime](/docs/Mocking-JsRuntime.html). For example: |
| 170 | + |
| 171 | +```csharp |
| 172 | +public class ComponentTest : ComponentTestFixture // implements the ITestContext interface |
| 173 | +{ |
| 174 | + [Fact] |
| 175 | + public void Test1() |
| 176 | + { |
| 177 | + // Add an custom service to the services collection |
| 178 | + Services.AddSingleton<IMyService>(new MyService()); |
| 179 | + |
| 180 | + // Add the Mock JsRuntime service |
| 181 | + Services.AddMockJsRuntime(); |
| 182 | + |
| 183 | + // Renders a MyComponent component and assigns the result to |
| 184 | + // a cut variable. CUT is short for Component Under Test. |
| 185 | + IRenderedComponent<MyComponent> cut = RenderComponent<MyComponent>(); |
| 186 | + } |
| 187 | +} |
| 188 | +``` |
| 189 | + |
| 190 | +See the page [Mocking JsRuntime](/docs/Mocking-JsRuntime.html) for more details mock. |
| 191 | + |
| 192 | +## Further reading |
| 193 | + |
| 194 | +To learn how to work with and assert against `IRenderedComponent`s visit the related pages: |
| 195 | + |
| 196 | +- [Working with rendered components and fragments](/docs/Working-with-rendered-components-and-fragments.html) |
| 197 | +- [Semantic HTML markup comparison](/docs/Semantic-HTML-markup-comparison.html) |
| 198 | +- [Mocking JsRuntime](/docs/Mocking-JsRuntime.html) |
0 commit comments