Skip to content

Commit ebd2271

Browse files
committed
docs: page on substitution
1 parent a6751ac commit ebd2271

3 files changed

Lines changed: 282 additions & 16 deletions

File tree

Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
1+
---
2+
uid: substituting-components
3+
title: Substituting (mocking) component
4+
---
5+
6+
# Substituting (mocking) components
7+
8+
bUnit makes it possible to substitute child components of a component under test with other components, e.g. mock components. This makes it possible to isolate a component under test from other components it depends on, e.g. 3rd party components.
9+
10+
To substitute a component during a test, you must register the substitute, or a substitute factory, with the `ComponentFactories` collection on bUnit's `TestContext`.
11+
12+
> [!NOTE]
13+
> This feature is only available for test projects that target .NET 5 or later.
14+
15+
The following sections will explain how to create substitute components and how to register them with the `ComponentFactories` collection.
16+
17+
## Creating substitute (mock) components
18+
19+
These are the requirements substitute components must meet:
20+
21+
1. A substitute component must implement `IComponent`, i.e. be a Blazor component.
22+
2. A substitute component must have the same parameters as the original component, _OR_ have a `CaptureUnmatchedValues` parameter that Blazor can pass all parameters to.
23+
3. _If_ the original component is assigned to a variable in component under test, e.g. via the `@ref` attribute, a substitute must be assignable to the original component (inherit from it).
24+
25+
Most popular mocking libraries are able to create substitute/mock components easily, based on the original component, that follow the requirement specified above.
26+
27+
If the substitute only has to match the two first requirements, bUnit's built-in `Stub<T>` can be used.
28+
29+
Finally, for complex scenarios, a hand-coded substitute component can be created.
30+
31+
### Substituting components with bUnit's `Stub<T>`
32+
33+
When the component that should be substitute out is not referenced in the component under test with `@ref`, use bUnit's built-in "stubbing" capability.
34+
35+
For example, supposed you want to test the `<Foo>` component and substitute out it's child component `<Bar>`:
36+
37+
```cshtml
38+
<Foo>
39+
<Bar />
40+
</Foo>
41+
```
42+
43+
To stub it out, use the `AddStub<T>()` method:
44+
45+
```csharp
46+
[Fact]
47+
public void Foo_Doesnt_Have_A_Bar_But_Stub()
48+
{
49+
using var ctx = new TestContext();
50+
51+
// Register the a stub substitution.
52+
ctx.ComponentFactories.AddStub<Bar>();
53+
54+
// Render the component under test.
55+
IRenderedFragment cut = ctx.Render(@<Foo />);
56+
57+
// Verify that the Bar component has
58+
// been substituted in the render tree.
59+
Assert.False(cut.HasComponent<Bar>());
60+
Assert.True(cut.HasComponent<Stub<Bar>>());
61+
}
62+
```
63+
64+
It is also possible to specify a base type/component for the component you want to substitute. For example, if `<Bar>` inherits from `<BarBase>`, you can specify `<BarBase>` and all components that inherit from `<BarBase>` will be substituted.
65+
66+
```csharp
67+
ctx.ComponentFactories.AddStub<BarBase>();
68+
```
69+
70+
To add substitute markup to the output, pass it in one of the following ways:
71+
72+
```csharp
73+
// Add the markup specified in the string to the rendered output
74+
// instead of that from <Bar>.
75+
ctx.ComponentFactories.AddStub<Bar>("<div>NOT FROM BAR</div>");
76+
77+
// Add the markup specified in the render fragment to the rendered
78+
// output instead of that from <Bar>.
79+
ctx.ComponentFactories.AddStub<Bar>(@div>NOT FROM BAR</div>);
80+
```
81+
82+
It is also possible to access the parameter that is passed to the substituted component, both when specifying alternative render output or when verifying the correct parameters was passed to the substituted component. For example, suppose `<Foo>` has a parameter named `Baz`:
83+
84+
```csharp
85+
// Add the markup specified in the template function to the rendered output
86+
// instead of that from <Bar>.
87+
ctx.ComponentFactories.AddStub<Bar>(parameters => $"<div>{parameters.Get(x => Baz)}</div>");
88+
89+
// Add the markup produced by the render template to the rendered
90+
// output instead of that from <Bar>.
91+
ctx.ComponentFactories.AddStub<Bar>(parameters => @div>@(parameters.Get(x => Baz))</div>);
92+
```
93+
94+
To verify that the expected value was passed to the `Baz` parameter of `<Foo>`, first find the substituted component in the render tree using the `FindComponent`/`FindComponents` methods, and then inspect the `Parameters` property. E.g.:
95+
96+
```csharp
97+
[Fact]
98+
public void Foo_Doesnt_Have_A_Bar_But_Stub()
99+
{
100+
using var ctx = new TestContext();
101+
ctx.ComponentFactories.AddStub<Bar>();
102+
103+
IRenderedFragment cut = ctx.Render(@<Foo />);
104+
105+
// Find the stubbed component in the render tree
106+
IRenderedComponent<Stub<Bar>> barStub = cut.FindComponent<Stub<Bar>>();
107+
108+
// Access parameters passed to it through the stubbed components
109+
// Parameters property, using the selector to pick out the parameter.
110+
var valuePassedToBaz = barStub.Instance.Parameters.Get(x => x.Baz);
111+
112+
// assert valuePassedToBaz is as expected...
113+
}
114+
```
115+
116+
#### Dynamic matching components to stub
117+
118+
To stub more than one component, e.g. all components from a 3rd party component library, pass a `Predicate<Type>` to the `AddStub` method, that returns `true` for all components that should be stubbed. For example:
119+
120+
```csharp
121+
// Stub all components of type `Bar`
122+
ctx.ComponentFactories.AddStub(type => type == typeof(Bar));
123+
124+
// Stub all components in the Third.Party.Lib namespace
125+
ctx.ComponentFactories.AddStub(type => type.Namespace == "Third.Party.Lib");
126+
```
127+
128+
It is also possible to specify replacement markup or a `RenderFragment` for components substituted using the "component predicate" method:
129+
130+
```csharp
131+
// Add the markup specified in the string to the rendered output
132+
// instead of the components that match the predicate,
133+
ctx.ComponentFactories.AddStub(type => type.Namespace == "Third.Party.Lib",
134+
"<div>NOT FROM BAR</div>");
135+
136+
// Add the markup produced by the render fragment to the rendered
137+
// output instead of the components that match the predicate.
138+
ctx.ComponentFactories.AddStub(type => type.Namespace == "Third.Party.Lib",
139+
@div>NOT FROM BAR</div>);
140+
```
141+
142+
### Creating a mock component with mocking libraries
143+
144+
To get more control over the substituted component or when having a reference to it in the component under test, use a mock created by a mocking library.
145+
146+
147+
Mocking libraries usually offer options of setting up expectations and specify responses to calls made to their methods and properties, as long as these are virtual.
148+
149+
> [!TIP]
150+
> To learn how to configure a mock object, consult your favorite mocking frameworks documentation.
151+
152+
#### Mocking limitations
153+
154+
Since the standard life-cycle methods in Blazor are all virtual, i.e. `OnInitialized` or `OnAfterRender`, etc., components in Blazor are generally very mock friendly.
155+
156+
However, if a mocked component has a *constructor*, *field* or *property initializers*, or implements `Dispose`/`DisposeAsync`, these will usually not be overridable by the mocking framework and will run when the component is instantiated and disposed.
157+
158+
If that is undesirable, consider creating a wrapper component around the component you wish to mock, or, if you own the component, avoid using a constructor and make use of virtual where ever possible.
159+
160+
#### Mocking examples
161+
162+
Supposed you want to test the `<Foo>` component and substitute out it's child component `<Bar>`:
163+
164+
```cshtml
165+
<Foo>
166+
<Bar />
167+
</Foo>
168+
```
169+
170+
Here are two examples of using the [Moq](https://github.com/Moq) and [NSubstitute](https://github.com/nsubstitute/NSubstitute) mocking libraries to substitute `<Bar>`:
171+
172+
# [MOQ](#tab/moq)
173+
174+
```csharp
175+
[Fact]
176+
public void Foo_Doesnt_Have_A_Bar_But_Mock()
177+
{
178+
using var ctx = new TestContext();
179+
180+
// Register the mock instance for Bar
181+
Mock<Bar> barMock = new Mock<Bar>();
182+
ctx.ComponentFactories.Add<Bar>(barMock.Object);
183+
184+
// Render the component under test
185+
IRenderedFragment cut = ctx.Render(@<Foo />);
186+
187+
// Verify that the Bar component has
188+
// been substituted in the render tree
189+
IRenderedComponent<Bar> bar = cut.FindComponent<Bar>();
190+
Assert.Same(barMock.Object, bar.Instance);
191+
}
192+
```
193+
194+
Moq the exposes the mocked component instance through the `Object` property.
195+
196+
# [NSubstitute](#tab/nsubstitute)
197+
198+
```csharp
199+
[Fact]
200+
public void Foo_Doesnt_Have_A_Bar_But_Mock()
201+
{
202+
using var ctx = new TestContext();
203+
204+
// Register the mock instance for Bar
205+
Bar barMock = Substitute.For<FancyParagraph>();
206+
ctx.ComponentFactories.Add<Bar>(barMock);
207+
208+
// Render the component under test
209+
IRenderedFragment cut = ctx.Render(@<Foo />);
210+
211+
// Verify that the Bar component has
212+
// been substituted in the render tree
213+
IRenderedComponent<Bar> bar = cut.FindComponent<Bar>();
214+
Assert.Same(barMock, bar.Instance);
215+
}
216+
```
217+
218+
***
219+
220+
> [!WARNING]
221+
> A mock instance can only be used once, i.e. can only be used substitute a single component in the render tree. To substitute more components with one `Add` call on `ComponentFactories`, pass a mock component factory in instead. See below for example.
222+
223+
To mock multiple components of the same type, pass in a mocking component factory:
224+
225+
# [MOQ](#tab/moq)
226+
227+
```csharp
228+
// Register a mock component factory to replace multiple Bar components
229+
ctx.ComponentFactories.Add<Bar>(() => Mock.Of<Bar>());
230+
```
231+
232+
# [NSubstitute](#tab/nsubstitute)
233+
234+
```csharp
235+
// Register a mock component factory to replace multiple Bar components
236+
ctx.ComponentFactories.Add<Bar>(() => Substitute.For<FancyParagraph>()));
237+
```
238+
239+
***
240+
241+
To mock components conditionally, pass a type predicate to the add method, along with a mock component factory. The mock component factory will be passed the type to create a mock of.
242+
243+
In the example below, an extension method is used to create a mock using Moq with reflection. The example also uses Moq's `MockRepository` type that makes it possible to set up the mocked components separately from when they are created. Other mocking frameworks may need similar helper method:
244+
245+
```csharp
246+
var mockRepo = new MockRepository(MockBehavior.Loose);
247+
ctx.ComponentFactories.Add(type => type.Namespace == "Thrid.Party.Lib",
248+
type => mockRepo.CreateComponent(type));
249+
```
250+
251+
And this is the extension method that can create mock components dynamically based on a type:
252+
253+
```csharp
254+
// Extension method that can create mock components dynamically
255+
// based on a type.
256+
internal static class MockRepositoryExtensions
257+
{
258+
private static readonly MethodInfo CreateMethodInfo = typeof(MockRepository)
259+
.GetMethod(nameof(MockRepository.Create), Array.Empty<Type>());
260+
261+
public static IComponent CreateComponent(this MockRepository repository, Type type)
262+
{
263+
var genericCreateMethod = CreateMethodInfo.MakeGenericMethod(type);
264+
var mock = (Mock)genericCreateMethod.Invoke(repository, null);
265+
return (IComponent)mock.Object;
266+
}
267+
}
268+
```
269+
270+
### Shallow rendering
271+
272+
A popular technique in JavaScript-based frontend testing is "shallow rendering".
273+
274+
> _"Shallow rendering lets you render a component "one level deep" and assert facts about what its render method returns, without worrying about the behavior of child components, which are not instantiated or rendered"._ -- [React.js docs](https://reactjs.org/docs/shallow-renderer.html).
275+
276+
This is possible in bUnit as well, using the type predicate technique discussed above. For example, to shallow render `<Foo>` using the built-in stub in bUnit, do the following:
277+
278+
```csharp
279+
ctx.ComponentFactories.AddStub<Foo>(type => type != typeof(Foo));
280+
```
281+
282+
This will tell bUnit to stub out all components in the render tree that is NOT `<Foo>`. This can also be achieved using a mocking framework. See the example in the previous section above for how to dynamically create component mocks using Moq.

docs/site/docs/test-doubles/shallow-rendering.md

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

docs/site/docs/test-doubles/stubbing-components.md

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

0 commit comments

Comments
 (0)