Lesson 1.8: bUnit to Test Blazor Components

Testing source code is one part of what we need for our game. Since we’re building on Blazor, we’re going to need a way to test Blazor components as well. And while a lot of Blazor is just plain C# classes that extend and render controls. There is an infrastructure in place to render those controls with the right context. Luckily, there is an open source project for running and validating Blazor components. It’s called bUnit.

You can read up on bUnit on their site. There are many Getting started samples describing how to use the library to setup Blazor component tests. But let’s go through a couple of easy examples.

And we don’t have to learn all about bUnit now. We will continue to expand on our bUnit knowledge throughout this lesson series.

Add bUnit to Test Project

Like we did with Blazorise earlier, we’re going to install bUnit via the NuGet Package Manager. Launch NuGet Package Manager, by right clicking on the SimpleRPG.Game.Tests project, and then select Manage NuGet Packages.

Fig 1 – NuGet Package Manager for bunit

In the browse tab:

  • Search for bunit
  • Check the Include prerelease box (because bUnit is still in beta release at this time).
  • Select the main bUnit package and click the Install button.

First bUnit Test

Testing Blazor components is a little different from testing regular C# classes: Blazor components are rendered, they have the Blazor component life cycle, during which we can provide input to them and where they produce output.

We use bUnit to render the component we want to test, pass in parameters to it, inject services into it, and access the rendered component instance and the markup that was produced.

Rendering a component happens through bUnit’s TestContext, and the result of the rendering, a IRenderedComponent<TComponent>, provides access to the component instance and its markup.

We still have the NavMenu component in our game project, let’s use that to write our first couple of bUnit component tests. The NavMenu renders a header and a single button that navigates to the home page. Let’s create a new NavMenuTests.cs file with a test that validates the default rendering of the component.

using Bunit;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.DependencyInjection;
using SimpleRPG.Game.Shared;
using SimpleRPG.Game.Tests.Mocks;
using Xunit;

namespace SimpleRPG.Game.Tests.Shared
{
    public class NavMenuTests
    {
        [Fact]
        public void SimpleRender()
        {
            // arrange
            using var ctx = new TestContext();
            
            // act
            var cut = ctx.RenderComponent<NavMenu>();

            // assert
            var expected = @"<span class=""oi oi-home"" aria-hidden=""true""></span> Home";
            Assert.Contains(expected, cut.Markup);
            expected = @"<div class=""collapse"" blazor:onclick=""2"">";
            Assert.Contains(expected, cut.Markup);
        }
    }
}

As you can see the test creates a new bUnit.TestContext. This holds all of the configuration needed to render a Blazor component. The second line actually renders the NavMenu control. And the validations, assert that there is a home button and the tabs are collapsed in the rendered HTML. This is an easy test, but shows us how to use bUnit to write a component test.

If we actually try to run this test now, it fails with an exception:

System.InvalidOperationException : Cannot provide a value for property 'NavigationManger' on type 'Microsoft.AspNetCore.Components.Routing.NavLink'. There is no registered service of type 'Microsoft.AspNetCore.Components.NavigationManager'.

Injecting required services

As we can decipher from the exception, the NavMenu component has a dependency on the NavigationManager service, but it does not exist in the component. Blazor has the @inject directive, which works with the dependency injection framework to provide shared services to all components and pages. We need to add this service into the bUnit.TestContext so that it can be used during the RenderComponent call.

Create MockNavigationManager

First, we will create a MockNavigationManager that supports similar behavior to NavigationManager, but simplified for our testing purposes. Mocks are classes that mimic system behavior for components being tested, but in controlled ways – without all of the implementation details and dependencies of the original.

Let’s create the code in MockNavigationManager.cs:

using Microsoft.AspNetCore.Components;

namespace SimpleRPG.Game.Tests.Mocks
{
    public class MockNavigationManager : NavigationManager
    {
        public MockNavigationManager()
        {
            this.Initialize("https://test.com/", "https://test.com/testlink/");
        }

        protected override void NavigateToCore(string uri, bool forceLoad)
        {
            this.Uri = uri;
        }
    }
}

This mock class derives from the ASP.NET NavigationManager and simply overrides the NavigateToCore method. It caches the requested Uri, but doesn’t actually perform a navigation since during our test we don’t actually want to switch pages. We just want to ensure our code would navigate as expected. In our test case, we can then call the NavigationManager.Uri property to validate that we are navigating to the correct page.

Finally, the constructor just initializes the MockNavigationManager with some default links.

Use the MockNavigationManager

Now this service is ready to be used in our test case. We need to update our test to add this service into the testing context:

        [Fact]
        public void SimpleRender()
        {
            // arrange
            using var ctx = new TestContext();
            ctx.Services.AddSingleton<NavigationManager>(new MockNavigationManager());

            // act
            var cut = ctx.RenderComponent<NavMenu>();

            // assert
            var expected = @"<span class=""oi oi-home"" aria-hidden=""true""></span> Home";
            Assert.Contains(expected, cut.Markup);
            expected = @"<div class=""collapse"" blazor:onclick=""2"">";
            Assert.Contains(expected, cut.Markup);
        }

The AddSingleton method places an instance of MockNavigationManager into the TestContext. And it registers that instance to return when the system requests a NavigationManager. This is how we get the NavMenu to receive our MockNavigationManager without knowing anything about our test classes. The Dependency Injection in Blazor is very powerful and allows us to build code and components that are testable.

When we run the test now, it builds and runs successfully. Congratulations, we have our first bUnit component test.

Testing Component Interactivity

Testing how the component renders is interesting, but most components also provide elements to allow users to interact with the page. For example, NavMenu lets you expand and collapse the elements within it. Let’s write a test that validates the collapse/expand state by simulating the click of the expander div in the component.

        [Fact]
        public void ToggleNavMenu()
        {
            // arrange
            using var ctx = new TestContext();
            ctx.Services.AddSingleton<NavigationManager>(new MockNavigationManager());

            // act
            var cut = ctx.RenderComponent<NavMenu>();
            cut.Find(".navbar-toggler").Click();

            // assert
            var expected = @"<div class="""" blazor:onclick=""2"">";
            Assert.Contains(expected, cut.Markup);
        }

This second test looks very similar in structure and content to our first test. And, we added a line to mimic the Click of an element with the CSS class name of “navbar-toggler”. In response to that Click event, the component is rendered again and our expectation changes to not include the “collapse” class name. If we run the NavMenu, this difference makes a section of the menu expand and collapse based on that class name’s presence.

Great, now we have an initial render test and an interactivity test. These will be the basis for many of our component tests going forward.

Testing user interface components can be difficult in many platforms like WPF, UWP (Universal Windows Platform), etc. Blazor does a great job of making its component model testable. And bUnit uses that infrastructure to build a powerful component test framework. We can test the various states of our game and ensure the rendered HTML is exactly what we expect.

We now have our testing tools in place and ready to do more coding.

9 thoughts on “Lesson 1.8: bUnit to Test Blazor Components

  1. Hi. To pass the final test, the code should be modified as follows.
    current –> var expected = @””;
    working code –> var expected = @””;

    I’ve never used bunit before, and it seems to be very useful for blazor testing. Thanks for sharing

    Like

  2. When I went through this I got two errors:
    Severity Code Description Project File Line Suppression State
    Error CS0234 The type or namespace name ‘Shared’ does not exist in the namespace ‘SimpleRPG.Game’ (are you missing an assembly reference?) NSCRPG.Game.Tests C:\Users\enemy\source\repos\nsc-rpg-game\nsc-rpg-game\NSCRPG.Game.Tests\NavMenuTests.cs 4 Active

    Severity Code Description Project File Line Suppression State
    Error CS0246 The type or namespace name ‘NavMenu’ could not be found (are you missing a using directive or an assembly reference?) NSCRPG.Game.Tests C:\Users\enemy\source\repos\nsc-rpg-game\nsc-rpg-game\NSCRPG.Game.Tests\NavMenuTests.cs 20 Active

    Like

  3. Hmm, now I’m getting this test failure:
    Test Name: NSCRPG.Game.Tests.Shared.NavMenuTests.ToggleNavMenu
    Test FullName: NSCRPG.Game.Tests.NSCRPG.Game.Tests.Shared.NavMenuTests.NSCRPG.Game.Tests.Shared.NavMenuTests.ToggleNavMenu
    Test Source: C:\Users\enemy\source\repos\nsc-rpg-game\nsc-rpg-game\NSCRPG.Game.Tests\NavMenuTests.cs : line 30
    Test Outcome: Failed
    Test Duration: 0:00:00

    Test Name: NSCRPG.Game.Tests.Shared.NavMenuTests.ToggleNavMenu
    Test Outcome: Failed
    Result StackTrace: at NSCRPG.Game.Tests.Shared.NavMenuTests.ToggleNavMenu() in C:\Users\enemy\source\repos\nsc-rpg-game\nsc-rpg-game\NSCRPG.Game.Tests\NavMenuTests.cs:line 42
    Result Message:
    Assert.Contains() Failure
    Not found:
    In value:
    NSCRPG.Game


    Home

    Like

    1. It looks like the output text may not match the expected text string in the test result. If you changed name of things in the NavMenu in Blazor, then you may need to update the expected results in the NavMenuTests as well.

      To debug this, in the test results window select the failing test and look at the Test Detail Summary section on the tool window. There will be debug output from the test that should have more information about why the test failed. If that doesn’t help, set a break point in the test and debug the test to see why the test is failing.

      Liked by 1 person

  4. THANKS!
    I’m writing a test for NavMenu and I got stuck on the System.InvalidOperationException error for a long time. I solved it by implementing the MockNavigationManager class. Thank you very much

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s