Table of Contents

Faking NavigationManager

bUnit has a fake version of Blazor's NavigationManager built-in, which is added by default to bUnit's TestContext.Services service provider. That means nothing special is needed to test components that depend on NavigationManager, as it is already available by default.

Verify NavigationManager interactions

Lets look at a few examples that show how to verify that a component correctly interacts with the NavigationManager in various ways.

In the examples, we'll use the following <PrintCurrentUrl> component:

@implements IDisposable
@inject NavigationManager NavMan

<button @onclick="() => NavMan.NavigateTo(GoToUrl)"></button>
<p>@url</p>

@code {
  private string url;

  [Parameter] public string GoToUrl { get; set; } = string.Empty;

  protected override void OnInitialized()
    => NavMan.LocationChanged += OnLocationChanged;

  public void Dispose()
    => NavMan.LocationChanged -= OnLocationChanged;

  private void OnLocationChanged(object? sender, LocationChangedEventArgs e)
  {
    url = e.Location;
    StateHasChanged();
  }
}

To verify that the <PrintCurrentUrl> component correctly listens to location changes, do the following:

// Arrange
var navMan = Services.GetRequiredService<FakeNavigationManager>();
var cut = RenderComponent<PrintCurrentUrl>();

// Act - trigger a navigation change
navMan.NavigateTo("newUrl");

// Assert - inspects markup to verify the location change is reflected there
cut.Find("p").MarkupMatches($"<p>{navMan.BaseUri}newUrl</p>");

To verify that the <PrintCurrentUrl> component correctly calls NavigateTo when it's button is clicked, do the following:

// Arrange
var cut = RenderComponent<PrintCurrentUrl>(parameters => parameters
  .Add(p => p.GoToUrl, "http://localhost/foo"));

// Act - trigger a location change by clicking the button
cut.Find("button").Click();

// Assert - inspect the navigation manager to see if its Uri has been updated.
var navMan = Services.GetRequiredService<FakeNavigationManager>();
Assert.Equal("http://localhost/foo", navMan.Uri);

If a component issues multiple NavigateTo calls, then it is possible to inspect the navigation history by accessing the History property. It's a stack based structure, meaning the latest navigations will be first in the collection at index 0.

Asserting that navigation was prevented with the NavigationLock component

The NavigationLock component, which was introduced with .NET 7, gives the possibility to intercept the navigation and can even prevent it. bUnit will always create a history entry for prevented or even failed interceptions. This gets reflected in the NavigationHistory property.

A component can look like this:

@inject NavigationManager NavigationManager

<button @onclick="(() => NavigationManager.NavigateTo("/counter"))">Counter</button>

<NavigationLock OnBeforeInternalNavigation="InterceptNavigation"></NavigationLock>

@code {
  private void InterceptNavigation(LocationChangingContext context)
  {
    context.PreventNavigation();
  }
}

A typical test, which asserts that the navigation got prevented, would look like this:

var navMan = Services.GetRequiredService<FakeNavigationManager>();
var cut = RenderComponent<InterceptComponent>();

cut.Find("button").Click();

// Assert that the navigation was prevented
var navigationHistory = navMan.History.Single();
Assert.Equal(NavigationState.Prevented, navigationHistory.NavigationState);

Simulate preventing navigation from a <a href> with the NavigationLock component

As <a href> navigation is not natively supported in bUnit, the NavigationManager can be used to simulate the exact behavior.

<a href="/counter">Counter</a>

<NavigationLock OnBeforeInternalNavigation="InterceptNavigation"></NavigationLock>

@code {
  private void InterceptNavigation(LocationChangingContext context)
  {
    throw new Exception();
  }
}

The test utilizes the NavigationManager itself to achieve the same:

var navMan = Services.GetRequiredService<FakeNavigationManager>();
var cut = RenderComponent<InterceptAHRefComponent>();

navMan.NavigateTo("/counter");

// Assert that the navigation was prevented
var navigationHistory = navMan.History.Single();
Assert.Equal(NavigationState.Faulted, navigationHistory.NavigationState);
Assert.NotNull(navigationHistory.Exception);

Getting the result of NavigationManager.NavigateToLogin

NavigationManager.NavigateToLogin is a function, which was introduced with .NET 7, which allows to login dynamically. The function can also retrieve an InteractiveRequestOptions object, which can hold additional parameter.

InteractiveRequestOptions requestOptions = new()
{
    Interaction = InteractionType.SignIn,
    ReturnUrl = NavigationManager.Uri,
};
requestOptions.TryAddAdditionalParameter("prompt", "login");
NavigationManager.NavigateToLogin("authentication/login", requestOptions);

A test could look like this:

var navigationManager = Services.GetRequiredService<FakeNavigationManager>();

ActionToTriggerTheNavigationManager();

// This helper method retrieves the InteractiveRequestOptions object
var requestOptions = navigationManager.History.Last().StateFromJson<InteractiveRequestOptions>();
Asser.NotNull(requestOptions);
Assert.Equal(requestOptions.Interaction, InteractionType.SignIn);
options.TryGetAdditionalParameter("prompt", out string prompt);
Assert.Equal(prompt, "login");
Progress Telerik

Premium sponsor: Progress Telerik.

Packt

Editorial support provided by Packt.

.NET Foundation

Supported by the .NET Foundation.