Skip to main content

Command Palette

Search for a command to run...

Three ways to handle third-party components when writing bUnit tests

Updated
5 min read
H

Hi, I’m Hans, a .NET developer from the Netherlands with over six years of experience, mostly working with cloud technologies. I’m certified in Azure (AZ-104, AZ-204, AZ-400, AZ-305, AI-102), CKAD, and PSM1, and I’m also a Microsoft Certified Trainer (MCT), so I enjoy sharing what I’ve learned along the way.

I’m interested in just about every new tech that comes my way, which keeps me constantly learning. It’s a bit of a blessing and a curse, but it keeps things interesting.

Outside of work, I’m really into strength training and try to hit the gym four times a week. It helps me stay focused and balanced. On this blog, I’ll be sharing my thoughts, tips, and lessons learned from my work in .NET and Azure, and hopefully, it’ll be useful to anyone on a similar path.

Writing trustworthy and reliable tests for your Blazor components can be hard. Especially if you’re heavy relaying on third-party components. In this short blog post I want to provide three ways to handle third-party components when writing bUnit tests. I assume you already know bUnit. If not, please check out their docs at https://bunit.dev/.

For all demo code in this project, I’m using xUnit as testing framework and MudBlazor as example third-party component library. Though, every option is also viable with other libraries. Lets dive straight into the first option!

Option 1 - Loading the third-party components

Within our bUnit tests, we can actually load in our third-party components as we normally would within our Blazor application.

public CheckoutCartTests()
{
    // We need to add these so we can use MudBlazor components correctly
    Services.AddMudServices();
    JSInterop.Mode = JSRuntimeMode.Loose;
}

As you can see, we add the default MudBlazor services and we then set the mode of the JSInterop to loose. The JSInterop is provided by bUnit and makes it possible to mock responses from JS. This is necessary because bUnit does not run any JS. By default the mode is set to Strict which will throw an exception if any JS is invoked that is not mocked. By setting it to Loose we tell it to just ignore any calls to the JS and return the default value if a JS method is called.

This option will work, though it might have some serious downsides. To illustrate I’ve provided a short list below.

✅ Upsides❌ Downsides
Easy and fast to setupCan make tests brittle because they can become depended on the third-party library if not careful
Requires minimal mockingWon’t really work if the third-party components are heavily depended on JS. We should then mock every method call, though this makes the tests brittle
Most production-like testMakes it hard to really unit test because we’re loading more than just the unit

Personally I’m not a big fan of this option because of the downsides. Though, I get it when people use it. I would recommend using this option only if the other options are to hard to implement our if you want a production-like test. Though, if you want a production-like test, I would recommend something like PlayWright for end-to-end tests.

Option 2 - Mocking the third-party components

Instead of fully loading in all the third-party components, bUnit offers a great way to mock these components by using the build in ComponentFactories.The ComponentFactories contains a method called AddStub which accepts different parameters. We can for example stub or what I like to call mock all components from the MudBlazor namespace like this:

ComponentFactories.AddStub(type => type.Namespace?.StartsWith("MudBlazor") == true);

When you add this line, all components within the MudBlazor namespace will be stubbed. Per default this will mean it will just render nothing. It will only create empty components of the type Stub<componentType>.

This is great because now our test is not reliant on the third-party components, though it also has a issue. If we use the line above, no third-party components will be rendered including their child content. Personally I use MudBlazor a lot, which offers things like MudGrid and MudContainer. These components help me build a layout fast but their child content often is my own code which I would like to test. So to actually do this, we can add specific stubs for each component we like to stub different.

ComponentFactories.AddStub(type => type.Namespace?.StartsWith("MudBlazor") == true);

// Stub MudBlazor components but preserve their child content
ComponentFactories.AddStub<MudContainer>(parameters => 
    @<div class="mud-container-stub">@parameters.Get(x => x.ChildContent)</div>);

ComponentFactories.AddStub<MudGrid>(parameters => 
    @<div class="mud-grid-stub">@parameters.Get(x => x.ChildContent)</div>);

The code above will actually fix our issue because this will stub all MudBlazor components, but for the MudContainer and the MudGrid it will actually load in a div element with it’s child content. This works because the ComponentsFactories works on a last-added order, meaning the latest added stub is checked first.

✅ Upsides❌ Downsides
Resilient tests because their is no dependency on the third-party librarySetup can be quite cumbersome if a lot of third-party components need specific stubbing
Gives fine-grained control on how third-party components are renderedCan make the component behave very different than production because it doesn’t load the third-party components the same way
Great for specific testing of your own components logic

Option 3 - Shallow rendering

The final option Shallow rendering is actually quite similar to option 2. Though the big difference is we invert our if statement like this:

ComponentFactories.AddStub(type => type != typeof(ComponentTypeWeWantToTest));

What this code does, it actually stubs everything that is not the same type of the component you’re trying to test. This will result nothing being rendered except the component itself. This is a great way if you just want to test some logic of the component itself and do not care about any child components.

✅ Upsides❌ Downsides
Resilient tests because their is no dependency on other componentsCan give issues if your component is heavily reliant on child components
Easy and fast to setupCan make the component behave very different than production because it doesn’t load any other components
Great for specific testing of your own components logic

Conclusion

To summarize, there are three main ways I personally like to test my Blazor components. When writing unit tests for them, I highly recommend option 3 if possible and if there are a lot of dependencies to child components I would recommend option 2. Option 1 is my personal least favorite because of the dependency that is created within your test, which can make the test very brittle.

In this post we quickly went over three ways to handle third party components. Make sure to check out the official documentation at https://bunit.dev/ for a more detailed overview of the features bUnit and the ComponentFactories have to offer.

If you got any cool additions or questions, feel free to reach out. I would love to hear how you all test your Blazor applications.

For now, happy coding!