Starting from .NET 7, Blazor WASM has changed how authentication works. Everything is described here. This authentication relies on several components such as the IRemoteAuthenticationService and the RemoteAuthenticatorView component.
On the other hand, bUnit has been the most popular framework for unit testing blazor components.
For one of the WASM apps I am working on, the authentication components is as follows:
@attribute [AllowAnonymous]
@page "/authentication/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
<div class="h-screen w-screen flex items-center justify-center">
<div class="w-[500px]">
<div>
<div class="flex space-x-2">
<div>
<svg class="w-5 h-5 text-gray-1 animate-spin" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
</div>
<div class="flex items-center justify-center" id="authentication-state">
<RemoteAuthenticatorView Action="@Action" />
</div>
</div>
</div>
</div>
</div>
@code {
[Parameter] public string? Action { get; set; }
}
However, when I tried unit testing this page, I was getting the following error:
Cannot provide a value for property 'AuthenticationService' on type 'Microsoft.AspNetCore.Components.WebAssembly.Authentication.RemoteAuthenticatorView'. There is no registered service of type 'Microsoft.AspNetCore.Components.WebAssembly.Authentication.IRemoteAuthenticationService`1[Microsoft.AspNetCore.Components.WebAssembly.Authentication.RemoteAuthenticationState]'
This clearly significates that my unit test needed some setup to make the RemoteAuthenticationView work.
After some research, and using Moq and FluentAssertions, I figured out how to make this possible. The UT is given by the snippet below:
[Fact]
public void Authentication_works()
{
Services.AddRemoteAuthentication<RemoteAuthenticationState, RemoteUserAccount, RemoteAuthenticationUserOptions>(conf =>
{
});
Mock<IRemoteAuthenticationService<RemoteAuthenticationState>> mock = new Mock<IRemoteAuthenticationService<RemoteAuthenticationState>>();
mock.Setup(x => x.CompleteSignInAsync(It.IsAny<RemoteAuthenticationContext<RemoteAuthenticationState>>()))
.ReturnsAsync(new RemoteAuthenticationResult<RemoteAuthenticationState>());
mock.Setup(x => x.SignInAsync(It.IsAny<RemoteAuthenticationContext<RemoteAuthenticationState>>()))
.ReturnsAsync(new RemoteAuthenticationResult<RemoteAuthenticationState>());
Services.AddScoped<IRemoteAuthenticationService<RemoteAuthenticationState>>(_ => mock.Object);
var cut = RenderComponent<Authentication>(prms => prms.Add(p => p.Action, "login"));
var authenticationParagraph = cut.Find("div#authentication-state").FirstChild;
authenticationParagraph.Should().NotBeNull();
authenticationParagraph?.TextContent.Should().Be("Checking login state...");
}
The first lines are about mocking the required services that make the RemoteAuthenticationView work. Line 15 simulates a login action. Line 16 to 18 checks for a paragraph that is generated by the RemoteAuthenticationView and that should contain the text Checking login state...
Enjoy