Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 30 additions & 10 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,29 +12,47 @@ The following section list all changes in beta-11.
List of new features.

- Two new overloads to the `RenderFragment()` and `ChildContent()` component parameter factory methods have been added that takes a `RenderFragment` as input. By [@egil](https://github.com/egil) in [#203](https://github.com/egil/bUnit/pull/203).

- Added a `ComponentParameterCollection` type. The `ComponentParameterCollection` is a collection of component parameters, that knows how to turn those components parameters into a `RenderFragment`, which will render a component and pass any parameters inside the collection to that component. That logic was spread out over multiple places in bUnit, and is now owned by the `ComponentParameterCollection` type. By [@egil](https://github.com/egil) in [#203](https://github.com/egil/bUnit/pull/203).

- Added additional placeholder services for `NavigationManager`, `HttpClient`, and `IStringLocalizer`, to make it easier for users to figure out why a test is failing due to missing service registration before rendering a component. By [@joro550](https://github.com/joro550) in [#223](https://github.com/egil/bUnit/pull/223).

- Added `Key` class that represents a keyboard key and helps to avoid constructing `KeyboardEventArgs` object manually. The key can be passed to `KeyPress`, `KeyDown`, or `KeyUp` helper methods to raise keyboard events. The `Key` class provides static special keys or can be obtained from character or string. Keys can be combined with key modifiers: `Key.Enter + Key.Alt`.

For example, this makes it easier to trigger keyboard events on an element:
For example, this makes it easier to trigger keyboard events on an element:

```csharp
var cut = ctx.RenderComponent<ComponentWithKeyboardEvents>();
var element = cut.Find("input");

element.KeyDown(Key.Enter + Key.Control); // Triggers onkeydown event with Ctrl + Enter
element.KeyUp(Key.Control + Key.Shift + 'B'); // Triggers onkeyup event with Ctrl + Shift + B
element.KeyPress('1'); // Triggers onkeypress event with key 1
element.KeyDown(Key.Alt + "<"); // Triggers onkeydown event with Alt + <
```

```csharp
var cut = ctx.RenderComponent<ComponentWithKeyboardEvents>();
var element = cut.Find("input");
By [@duracellko](https://github.com/duracellko) in [#101](https://github.com/egil/bUnit/issues/101).

element.KeyDown(Key.Enter + Key.Control); // Triggers onkeydown event with Ctrl + Enter
element.KeyUp(Key.Control + Key.Shift + 'B'); // Triggers onkeyup event with Ctrl + Shift + B
element.KeyPress('1'); // Triggers onkeypress event with key 1
element.KeyDown(Key.Alt + "<"); // Triggers onkeydown event with Alt + <
```
By [@duracellko](https://github.com/duracellko) in [#101](https://github.com/egil/bUnit/issues/101).
- Added support for registering/adding "layout" components to a test context, which components should be rendered inside. This allows you to simplify the "arrange" step of a test when a component under test requires a certain render tree as its parent, e.g. a cascading value.

For example, to pass a cascading string value `foo` to all components rendered with the test context, do the following:

```csharp
ctx.AddLayoutComponent<CascadingValue<string>>(parameters => parameters.Add(p => p.Value, "foo"));
var cut = ctx.RenderComponent<ComponentReceivingFoo>();
```

By [@duracellko](https://github.com/duracellko) in [#101](https://github.com/egil/bUnit/issues/101).

### Changed
List of changes in existing functionality.

- The `ComponentParameterBuilder` has been renamed to `ComponentParameterCollectionBuilder`, since it now builds the `ComponentParameterCollection` type, introduced in this release of bUnit. By [@egil](https://github.com/egil) in [#203](https://github.com/egil/bUnit/pull/203).

- `ComponentParameterCollectionBuilder` now allows adding cascading values that is not directly used by the component type it targets. This makes it possible to add cascading values to children of the target component. By [@egil](https://github.com/egil) in [#203](https://github.com/egil/bUnit/pull/203).

- The `Add(object)` has been replaced by `AddCascadingValue(object)` in `ComponentParameterCollectionBuilder`, to make it more clear that an unnamed cascading value is being passed to the target component or one of its child components. It is also possible to pass unnamed cascading values using the `Add(parameterSelector, value)` method, which now correctly detect if the selected cascading value parameter is named or unnamed. By [@egil](https://github.com/egil) in [#203](https://github.com/egil/bUnit/pull/203).

- It is now possible to call the `Add()`, `AddChildContent()` methods on `ComponentParameterCollectionBuilder`, and the factory methods `RenderFragment()`, `ChildContent()`, and `Template()`, _**multiple times**_ for the same parameter, if it is of type `RenderFragment` or `RenderFragment<TValue>`. Doing so previously would either result in an exception or just the last passed `RenderFragment` to be used. Now all the provided `RenderFragment` or `RenderFragment<TValue>` will be combined at runtime into a single `RenderFragment` or `RenderFragment<TValue>`.

For example, this makes it easier to pass e.g. both a markup string and a component to a `ChildContent` parameter:
Expand All @@ -50,7 +68,9 @@ List of changes in existing functionality.
);
```
By [@egil](https://github.com/egil) in [#203](https://github.com/egil/bUnit/pull/203).

- All test doubles are now in the same namespace, `Bunit.TestDoubles`. So all import statements for `Bunit.TestDoubles.JSInterop` and `Bunit.TestDoubles.Authorization` must be changed to `Bunit.TestDoubles`. By [@egil](https://github.com/egil) in [#223](https://github.com/egil/bUnit/pull/223).

- Marked MarkupMatches methods as assertion methods to stop SonarSource analyzers complaining about missing assertions in tests. By [@egil](https://github.com/egil) in [#229](https://github.com/egil/bUnit/pull/229).

### Deprecated
Expand Down
1 change: 1 addition & 0 deletions docs/.editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ trim_trailing_whitespace = false

[*.{cs,razor}]
tab_size = 2
indent_style = space
dotnet_diagnostic.BL0001.severity = none
dotnet_diagnostic.BL0002.severity = none
dotnet_diagnostic.BL0003.severity = none
Expand Down
6 changes: 6 additions & 0 deletions docs/samples/components/PrintCascadingValue.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Cascading value: @Value
@code
{
[CascadingParameter]
public string Value { get; set; }
}
2 changes: 1 addition & 1 deletion docs/samples/tests/mstest/BunitTestContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

namespace Bunit.Docs.Samples
{
public abstract class BunitTestContext : ITestContext, IDisposable
public abstract class BunitTestContext : IDisposable
{
private Bunit.TestContext _context;

Expand Down
2 changes: 1 addition & 1 deletion docs/samples/tests/nunit/BunitTestContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

namespace Bunit.Docs.Samples
{
public abstract class BunitTestContext : ITestContext, IDisposable
public abstract class BunitTestContext : IDisposable
{
private Bunit.TestContext _context;

Expand Down
2 changes: 1 addition & 1 deletion docs/samples/tests/razor/_Imports.razor
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@
@using Microsoft.Extensions.DependencyInjection
@using AngleSharp.Dom
@using Bunit
@using Bunit.TestDoubles.JSInterop
@using Bunit.TestDoubles
@using Xunit
2 changes: 1 addition & 1 deletion docs/samples/tests/xunit/InjectAuthServiceTest.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Bunit.TestDoubles.Authorization;
using Bunit.TestDoubles;
using System;
using Xunit;

Expand Down
48 changes: 48 additions & 0 deletions docs/samples/tests/xunit/RenderTreeTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using Xunit;
using Bunit;
using System.Collections.Generic;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.Extensions.DependencyInjection;

namespace Bunit.Docs.Samples
{
public class RenderTreeTest
{
[Fact]
public void PrintCascadingValueTest()
{
using var ctx = new TestContext();

// Add a cascading value to the test contexts root render tree.
ctx.RenderTree.Add<CascadingValue<string>>(parameters => parameters
.Add(p => p.Value, "FOO")
);

// The component will be rendered as a chld of last
// component added to the RenderTree property.
var cut = ctx.RenderComponent<PrintCascadingValue>();

// Verify that the cascading value was passed correctly.
cut.MarkupMatches($"Cascading value: FOO");
}

[Fact]
public void PrintCascadingValue2Test()
{
using var ctx = new TestContext();

// Add a cascading value to the test contexts root render tree.
ctx.RenderTree.TryAdd<CascadingValue<string>>(parameters => parameters
.Add(p => p.Value, "BAR?")
);

// The component will be rendered as a chld of last
// component added to the RenderTree property.
var cut = ctx.RenderComponent<PrintCascadingValue>();

// Verify that the cascading value was passed correctly.
cut.MarkupMatches($"Cascading value: BAR?");
}
}
}
2 changes: 1 addition & 1 deletion docs/samples/tests/xunit/UserInfoTest.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Bunit.TestDoubles.Authorization;
using Bunit.TestDoubles;
using Xunit;

namespace Bunit.Docs.Samples
Expand Down
2 changes: 1 addition & 1 deletion docs/samples/tests/xunit/UserRightsTest.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Bunit.TestDoubles.Authorization;
using Bunit.TestDoubles;
using System.Security.Claims;
using System.Globalization;
using Xunit;
Expand Down
1 change: 1 addition & 0 deletions docs/samples/tests/xunit/bunit.docs.xunit.samples.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="../../../../src/bunit.core/bunit.core.csproj" />
<ProjectReference Include="../../../../src/bunit.web/bunit.web.csproj" />
<ProjectReference Include="../../../../src/bunit.xunit/bunit.xunit.csproj" />
<ProjectReference Include="../../components/bunit.docs.samples.csproj" />
Expand Down
1 change: 1 addition & 0 deletions docs/site/docs/providing-input/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ This section covers the various ways to provide input to a component under test.

- **<xref:passing-parameters-to-components>:** This covers passing regular parameters, child content, cascading values, event callbacks, etc. This topic is mostly relevant when writing tests in C# only.
- **<xref:inject-services>:** This covers injecting services into components under test. This topic is relevant for both Razor-based tests and C# only tests.
- **<xref:root-render-tree>:** This covers how to modify the root render tree that components under tests are rendered in.
- **<xref:configure-3rd-party-libs>:** This covers setting up 3rd party libraries in a bUnit testing scenario so that components under test that use them can be tested easily.
33 changes: 33 additions & 0 deletions docs/site/docs/providing-input/root-render-tree.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
uid: root-render-tree
title: Controlling the Root Render Tree
---

# Controlling the Root Render Tree

The root render tree, the render tree in which components under test are rendered, can be added to, before components are rendered with bUnit's test context. This is mostly useful when a component under test or a component it depends on, must be rendered inside another component, that provides a cascading value to it.

For example, when using Blazor’s authentication, it is common to add the `CascadingAuthenticationState` component higher up the render tree, such that it can provide authentication state to those components below it that needs it. Adding this through the <xref:Bunit.TestContext.RenderTree> property on the <xref:Bunit.TestContext> type makes it possible to add it once in a shared setup method, and not having to do so in every test method during the call to [`RenderComponent()`](xref:Bunit.TestContext.RenderComponent``1(System.Action{Bunit.ComponentParameterCollectionBuilder{``0}})).

This can also be useful when writing tests that use a 3rd party component library, that require a special root component to be added to the render tree, but which otherwise doesn’t change between tests.

## Adding a Component to the Root Render Tree

The following example verifies that the `PrintCascadingValue` component correctly prints out the cascading value passed to it. This value is passed to it by adding the `CascadingValue<string>` component to the render tree and then rendering the `PrintCascadingValue` component. The `PrintCascadingValue` component looks like this:

[!code-cshtml[PrintCascadingValue.razor](../../../samples/components/PrintCascadingValue.razor)]

Here is the test that adds the `CascadingValue<string>` component to the render tree and then renders the `PrintCascadingValue` component.

[!code-csharp[PrintCascadingValueTest.cs](../../../samples/tests/xunit/RenderTreeTest.cs#L15-L27)]

> [!NOTE]
> The call to [`Add`](xref:Bunit.Rendering.RootRenderTree.Add``1(System.Action{Bunit.ComponentParameterCollectionBuilder{``0}})) can be done in a common setup method, outside the context of the test method listed here, for easy reuse between tests.

## Add Only if Not Already in Root Render Tree

Sometimes common test setup logic exists outside the test class, perhaps abstracted away in other libraries. In those cases, the [`TryAdd`](xref:Bunit.Rendering.RootRenderTree.TryAdd``1(System.Action{Bunit.ComponentParameterCollectionBuilder{``0}})) can be used add the component to the render tree, _only if_ it has not already been added. [`TryAdd`](xref:Bunit.Rendering.RootRenderTree.TryAdd``1(System.Action{Bunit.ComponentParameterCollectionBuilder{``0}})) returns true if the component was added, false otherwise.

[!code-csharp[](../../../samples/tests/xunit/RenderTreeTest.cs#L36-L38)]

In the listing above, the cascading value `BAR?` is only added if there is not another `CascadingValue<string>` component added to the render tree already.
14 changes: 7 additions & 7 deletions docs/site/docs/test-doubles/faking-auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ The test implementation of Blazor's authentication and authorization can be put
- **Authenticated** and **authorized**
- **Authenticated** and **authorized** with one or more **roles**, **claims**, and/or **policies**

bUnit's authentication and authorization implementation is easily available by calling [`AddTestAuthorization()`](xref:Bunit.TestDoubles.Authorization.FakeAuthorizationExtensions.AddTestAuthorization(Bunit.TestServiceProvider)) on a test context's `Services` collection. This returns an instance of the <xref:Bunit.TestDoubles.Authorization.TestAuthorizationContext> type that allows you to control the authentication and authorization state for a test.
bUnit's authentication and authorization implementation is easily available by calling [`AddTestAuthorization()`](xref:Bunit.TestDoubles.FakeAuthorizationExtensions.AddTestAuthorization(Bunit.TestServiceProvider)) on a test context's `Services` collection. This returns an instance of the <xref:Bunit.TestDoubles.TestAuthorizationContext> type that allows you to control the authentication and authorization state for a test.

The following sections will show how to set each of these states in a test.

Expand Down Expand Up @@ -43,23 +43,23 @@ To set the state to authenticating and authorizing, do the following:

[!code-csharp[UserInfoTest.cs](../../../samples/tests/xunit/UserInfoTest.cs?start=26&end=36&highlight=4)]

After calling `AddTestAuthorization()`, the returned <xref:Bunit.TestDoubles.Authorization.TestAuthorizationContext> is used to set the authenticating and authorizing state through the <xref:Bunit.TestDoubles.Authorization.TestAuthorizationContext.SetAuthorizing> method.
After calling `AddTestAuthorization()`, the returned <xref:Bunit.TestDoubles.TestAuthorizationContext> is used to set the authenticating and authorizing state through the <xref:Bunit.TestDoubles.TestAuthorizationContext.SetAuthorizing> method.

### Authenticated and Unauthorized State

To set the state to authenticated and unauthorized, do the following:

[!code-csharp[UserInfoTest.cs](../../../samples/tests/xunit/UserInfoTest.cs?start=42&end=52&highlight=4)]

After calling `AddTestAuthorization()`, the returned <xref:Bunit.TestDoubles.Authorization.TestAuthorizationContext> is used to set the authenticated and unauthorized state through the <xref:Bunit.TestDoubles.Authorization.TestAuthorizationContext.SetAuthorized(System.String,Bunit.TestDoubles.Authorization.AuthorizationState)> method.
After calling `AddTestAuthorization()`, the returned <xref:Bunit.TestDoubles.TestAuthorizationContext> is used to set the authenticated and unauthorized state through the <xref:Bunit.TestDoubles.TestAuthorizationContext.SetAuthorized(System.String,Bunit.TestDoubles.AuthorizationState)> method.

### Authenticated and Authorized state

To set the state to authenticated and authorized, do the following:

[!code-csharp[UserInfoTest.cs](../../../samples/tests/xunit/UserInfoTest.cs?start=58&end=68&highlight=4)]

After calling `AddTestAuthorization()`, the returned <xref:Bunit.TestDoubles.Authorization.TestAuthorizationContext> is used to set the authenticated and authorized state through the <xref:Bunit.TestDoubles.Authorization.TestAuthorizationContext.SetAuthorized(System.String,Bunit.TestDoubles.Authorization.AuthorizationState)> method.
After calling `AddTestAuthorization()`, the returned <xref:Bunit.TestDoubles.TestAuthorizationContext> is used to set the authenticated and authorized state through the <xref:Bunit.TestDoubles.TestAuthorizationContext.SetAuthorized(System.String,Bunit.TestDoubles.AuthorizationState)> method.

Note, the second parameter, `AuthorizationState`, is optional, and defaults to `AuthorizationState.Authorized`, if not specified.

Expand All @@ -77,7 +77,7 @@ To specify one or more roles for the authenticated and authorized user, do the f

[!code-csharp[UserRightsTest.cs](../../../samples/tests/xunit/UserRightsTest.cs?start=29&end=42&highlight=5)]

The highlighted line shows how the <xref:Bunit.TestDoubles.Authorization.TestAuthorizationContext.SetRoles(System.String[])> method is used to specify one role. To specify multiple roles, do the following:
The highlighted line shows how the <xref:Bunit.TestDoubles.TestAuthorizationContext.SetRoles(System.String[])> method is used to specify one role. To specify multiple roles, do the following:

[!code-csharp[UserRightsTest.cs](../../../samples/tests/xunit/UserRightsTest.cs?start=48&end=62&highlight=5)]

Expand All @@ -87,7 +87,7 @@ To specify one or more policies for the authenticated and authorized user, do th

[!code-csharp[UserRightsTest.cs](../../../samples/tests/xunit/UserRightsTest.cs?start=68&end=81&highlight=5)]

The highlighted line shows how the <xref:Bunit.TestDoubles.Authorization.TestAuthorizationContext.SetPolicies(System.String[])> method is used to specify one policy. To specify multiple policies, do the following:
The highlighted line shows how the <xref:Bunit.TestDoubles.TestAuthorizationContext.SetPolicies(System.String[])> method is used to specify one policy. To specify multiple policies, do the following:

[!code-csharp[](../../../samples/tests/xunit/UserRightsTest.cs?start=91&end=91)]

Expand All @@ -97,7 +97,7 @@ To specify one or more claims for the authenticated and authorized user, do the

[!code-csharp[UserRightsTest.cs](../../../samples/tests/xunit/UserRightsTest.cs?start=106&end=123&highlight=5-8)]

The highlighted line shows how the <xref:Bunit.TestDoubles.Authorization.TestAuthorizationContext.SetClaims(System.Security.Claims.Claim[])> method is used to pass two instances of the `Claim` types.
The highlighted line shows how the <xref:Bunit.TestDoubles.TestAuthorizationContext.SetClaims(System.Security.Claims.Claim[])> method is used to pass two instances of the `Claim` types.

### Example of passing both roles, claims, and policies

Expand Down
1 change: 1 addition & 0 deletions docs/site/docs/toc.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
# [Providing Input](xref:providing-input)
## [Parameters and Cascading Values](xref:passing-parameters-to-components)
## [Inject Services into Components](xref:inject-services)
## [Controlling the Root Render Tree](xref:root-render-tree)
## [Configure 3rd Party Libraries](xref:configure-3rd-party-libs)

# [Interaction](xref:interaction)
Expand Down
Loading