Blazor Unit Testing – Dev Leader Weekly 14

Hey there!

Welcome to Dev Leader Weekly, your dose of software engineering and C# goodness! In this newsletter, we’ll explore thought-provoking ideas, byte-sized code examples, and highlight some interesting content from the week to help you excel as a software engineer.

After returning from my honeymoon with my wife, we both tested positive for COVID! What an absolute bummer. I’ve been totally drained having swinging fevers and not being able to breathe clearly at all. Fortunately for the two of us, I anticipate a speedy recovery, so we’ll take that as a positive given the circumstances.

A few housekeeping things:

  • I’ll be sharing an affiliate link list at the bottom of my newsletters so you can check out the products and services that I trust and use. There’s no requirement that you use any of these, but if they interest you then I’ll get some commission to keep my costs down on content creation.
  • I’ll be sharing with you a new newsletter referral opportunity! Referring folks to sign up for my newsletter will allow you to get free membership access for a few months to my site to access the newsletter archive and my Behind The Screen weekly vlogs that I do. If you’re reading this on my site: the referral link only works in the email!
  • And finally… I want to increase the value of this newsletter by putting together dedicated newsletter-only articles whenever I can. And I’m starting with this week where you’ll get to learn about unit testing in Blazor!

We’ll start things off with the exclusive article and follow up with the other newsletter goodies that I usually offer every week. I hope that sounds exciting, so let’s dive into it!


Blazor Unit Testing

Getting Set Up for Blazor Unit Testing

To get started with Blazor unit testing using bUnit and xUnit, you’ll need to have a few tools installed on your machine. If you’re reading this newsletter, I suspect you already have these things ready to go… But just to be sure, I wanted to cover them in more detail for newer folks.

First, you’ll need a copy of Microsoft’s Visual Studio integrated development environment (IDE). Visual Studio is a popular choice for C# developers, and it makes it easy to create and test Blazor applications. You should also install the .NET Core runtime, which is necessary for running and debugging .NET Core applications.

Once you have all the necessary tools installed, you can create a new Blazor project in Visual Studio. To do this, open Visual Studio and select “Create a New Project” from the start page. From there, select “Blazor App” from the list of project templates. You can choose either a client-side or server-side Blazor project, depending on your needs.

The Test Project

While there are no rules for this necessarily, I strongly encourage putting your tests into an entirely separate assembly. If it’s not something you want to ship with your core code, we can pull it out into a separate project. Let’s go ahead and make a second test project in our solution, a class library, and add a reference to the original project.

You’ll need to add bUnit and xUnit to the test project via NuGet. NuGet is a package manager for .NET that makes it easy to share and reuse code between projects. In Visual Studio, right-click on your project in the Solution Explorer pane and select “Manage NuGet Packages”. You can also do this on the command line if you’re comfortable with that!

From there, you can search for both bUnit and xUnit and install them into your project. I also install “Microsoft.NET.Test.Sdk” and “xunit.runner.visualstudio” packages for a full integration right in my IDE for running tests.

Once these are installed, you’re ready to start writing unit tests for your Blazor components! You can follow this article if you’re having challenges discovering tests in Visual Studio.

Blazor Unit Testing for Simple Components

One of the most important aspects that bUnit allows us to do is get a rendered version of the Blazor component. We can illustrate this with a simple example, where we have this razor component:

<h3>Component</h3>

@code {

}

And while that looks trivial, you can extend some of the patterns we’ll see to more complex examples. Given how simple this is, we’ll really only be able to check the h3 element we have, since that’s all the component is made of!

Here’s what a sample test class would look like:

using Bunit;

using ExampleBlazorApp.Components;

using Xunit;

namespace ExampleBlazorApp.Tests;

public class ComponentTests : TestContext
{
    [Fact]
    public void Header_DefaultState_ExpectedTitle()
    {
        var renderedComponent = RenderComponent<Component>();
        
        renderedComponent
            .Find("h3")
            .MarkupMatches("<h3>Component</h3>");
        Assert.Empty(renderedComponent.FindAll("h1"));
    }
}

You’ll see the [Fact] attribute that’s necessary for marking up simple xUnit tests, but also take note that our test class is inheriting from a bUnit TestContext. You don’t need to use inheritance to make this work, but I’m just demonstrating it in this code example.

The important part is that we can call the RenderComponent method from this TestContext and give it our Component type. The returned result is a reference that allows us to look for particular nodes and assert the markup is as we expect! Given the simplicity of this component, we can ensure our h3 says “Component” and just for good measure, we can check that there are no h1 tags on this Blazor component.

What About… A More Complex Example?

Okay, okay… That was too easy. If we want to unit test the counter label on the “Counter.razor” page that comes in the template new Blazor project from Visual Studio, we need to make some modifications. While this page does have a button on it that we can click (and yes, bUnit allows us to interact with controls!), I want to illustrate a state change reflecting a change in UI text without us pressing a button. It’s sort of the difference between a “unit” test and a “functional” test for UI elements, in my opinion.

Let’s check out the code:

@page "/counter"

<PageTitle>Counter</PageTitle>

<h1>Counter</h1>

<p role="status">Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    // to make it work with bUnit, we need to have:
    // - a public property with a getter and setter
    // - mark it with a [Parameter] attribute
    [Parameter]
    public int currentCount { get; set; }

    //private int currentCount;

    private void IncrementCount()
    {
        currentCount++;
    }
}

Spoiler alert: this isn’t my preferred way to do this, but I want to illustrate what bUnit lets us do. I’ll show you my preferred way after! Regardless, in the above code, we need to convert our private field into a public property with an attribute. Once we do that, we can use some code from bUnit to change the state of the component and re-render it so that we can see the newly updated value!

Here are a couple of tests that show this in action:

using Bunit;

using ExampleBlazorApp.Pages;

using Xunit;

namespace ExampleBlazorApp.Tests;

public class CounterPageTests : TestContext
{

    [Fact]
    public void CounterLabel_DefaultState_ExpectedValue()
    {
        var renderedComponent = RenderComponent<Counter>();
        renderedComponent
            .Find("p")
            .MarkupMatches("<p role="status">Current count: 0</p>");
    }

    [Fact]
    public void CounterLabel_CountIsSet_ExpectedValue()
    {
        var renderedComponent = RenderComponent<Counter>();
        
        renderedComponent.SetParametersAndRender(
            parameters => parameters.Add<int>(p => p.currentCount, 5));

        renderedComponent
            .Find("p")
            .MarkupMatches("<p role="status">Current count: 5</p>");
    }
}

The first test looks very similar to the previous example… But the second test uses SetParametersAndRender on the instance of the rendered component to change its state. After that, we can check the markup and see that it’s updated! Again, we could use bUnit to click the button x number of times, but this is more of a “functional” test. Both offer value for different reasons!

YouTube player

… And The Preferred Way?

I mentioned that’s not my preferred way to unit test a UI control. Building on some of my experience from writing both unit and functional tests in WPF for many years, we could instead use something like a “View Model” to go alongside our page. In truth, a pattern I like to follow isn’t even MVVM or MVC, but more like a combination of the two! But I digress… Let’s see how we can adjust this page to use something like a View Model to clean things up.

Our view model:

namespace ExampleBlazorApp.Pages;

public interface ICounterViewModel
{
    int CurrentCount { get; }

    void IncrementCount();
}

public sealed class CounterViewModel : ICounterViewModel
{
    public int CurrentCount { get; private set; }

    public void IncrementCount() =>
        CurrentCount++;
}

Do you need the interface AND the class? Not really, especially because this class is nearly just a data transfer object (DTO), but I’m going to be using Moq in the upcoming example. This is just to show you that if you have something more complex where mocking is handy, you can piece all of these together.

Here’s our adjusted page code:

@page "/counter2"
@inject ICounterViewModel CounterViewModel

<PageTitle>Counter 2</PageTitle>

<h1>Counter 2</h1>

<p role="status">Current count: @CounterViewModel.CurrentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private void IncrementCount() =>
        CounterViewModel.IncrementCount();
}

Now, let’s look at some test code. I’m going to switch up the pattern a bit where we don’t inherit from the TestContext class, but this is just to show you an alternative way this all works. We’ll instead create a new instance of it in our constructor, and we’re also going to need to register our view model as a service for our dependency container. I’m just calling these out so that you don’t miss them when looking at the test methods:

using Bunit;

using ExampleBlazorApp.Pages;

using Microsoft.Extensions.DependencyInjection;

using Moq;

using Xunit;

namespace ExampleBlazorApp.Tests
{
    public class CounterPage2Tests : IDisposable
    {
        private readonly MockRepository _mockRepository;
        private readonly TestContext _testContext;
        private readonly Mock<ICounterViewModel> _counterViewModel;

        public CounterPage2Tests()
        {
            _mockRepository = new MockRepository(MockBehavior.Strict);
            _counterViewModel = _mockRepository.Create<ICounterViewModel>();

            _testContext = new TestContext();
            _testContext.Services.AddSingleton(_counterViewModel.Object);
        }

        public void Dispose()
        {
            _testContext.Dispose();
        }

        [Fact]
        public void Header_DefaultState_ExpectedTitle()
        {
            _counterViewModel
                .Setup(x => x.CurrentCount)
                .Returns(0);

            var renderedComponent = _testContext.RenderComponent<Counter2>();
            renderedComponent
                .Find("h1")
                .MarkupMatches("<h1>Counter 2</h1>");

            _mockRepository.VerifyAll();
        }

        [InlineData(1)]
        [InlineData(3)]
        [InlineData(5)]
        [Theory]
        public void CounterLabel_CountIsSet_ExpectedValue(
            int counterValue)
        {
            _counterViewModel
                .Setup(x => x.CurrentCount)
                .Returns(counterValue);

            var renderedComponent = _testContext.RenderComponent<Counter2>();
            
            var expectedMarkup = $"<p role="status">Current count: {counterValue}</p>";
            renderedComponent
                .Find("p")
                .MarkupMatches(expectedMarkup);

            _mockRepository.VerifyAll();
        }
    }
}

The test Header_DefaultState_ExpectedTitle *does* require that we have our mock setup for the view model. Even though we’re not testing the label for the count in that test, in order to successfully render the component that property will need to be mocked on the view model.

In the second test, I’m using a [Theory] now just to be fancy. You’ll note that we set up our mock with the expected value before we render the component. This way, when we ask for the state it’s as expected with the correct count.

Doesn’t That Seem Like Overkill for Blazor Unit Testing?

Because of how I refactored the view model and the fact that the tests we were looking at didn’t use the button or IncrementCount method… this seems like a lot of bloat. An interface for something that’s almost a DTO. Mock object configurations. Yuck. Gross. Yes, it’s overkill in this example.

I’m demonstrating the different tools you have access to – and you can make decisions about testing units of code vs functional/integration paths of your code. If you want to skip the mock and just create an instance of the view model with the right value, go for it! Easy-peasy! But if you are interested in isolating more complex code you might have on your view model (or some type of hybrid view model and controller), then you can see how a mock might be used.

And with that said… you can now explore how to write your own Blazor unit tests with bUnit and xUnit (and Moq, if you want). No more excuses for leaving UIs untested in your applications! Your bonus for subscribing to my newsletter is early access to this Blazor unit testing video which goes public on Monday:

YouTube player

Thought-Provoking Quote of the Week:

Every week a little bit of insight from someone! Whether it’s me or something else I’ve come across to share with you, I hope you find it insightful:

Dev Leader Quotes - Proactively Learning

This week’s quote is from me (I’m trying to sprinkle more of these where I can). Its focus is on balance and continuous learning.

Let’s face it – as programmers and software engineers we love tech. We love new cool technologies, frameworks, and whatever is shiny (for nerds). It’s attractive to us and gets us excited. And there’s nothing wrong with that!

The other part to call out here is that we *should* be making time to keep up to date with what’s trending in tech. It’s helpful to see how different frameworks and tech stacks continue to grow and evolve. Perhaps there’s something completely new hitting the scene that changes how you might approach developing certain apps. Just imagine that you were hearing about the AI wave and ChatGPT for the first time… Scary, right?

But what about the last part of the quote? Why is that important?

Priorities.

Generally, one of the things we suck at as software engineers is that we like the shiny stuff so much that we’ll do anything we can to slap it into the things we’re building. We look for excuses to get the coolest new database tech, or rewrite everything in a language named after corroded metal. Most of the time, the reality is that it’s absolutely unnecessary.

Learning about new tech and being aware of it is different than finding every opportunity to sneak it into a project you’re building. So as you continue to understand the pros and cons of different tech as they’re evolving you can start making informed decisions about when to include it… and when to skip it. Maybe the next project is the right time. Or maybe by then, we have another shiny language to learn instead.


Featured Content:

Feedback Is HARD – So Let’s Improve It For Software Engineers!

YouTube player

This video focused on feedback advice for software engineers — primarily more junior ones. It’s not something that I feel like we’re taught how to work with, and we’re just sort of expected to know how to receive it and that’s that. But what about providing feedback? And how do we ACTUALLY make use of the feedback we receive? Check it out!

Are You Too Blunt? – 2 Communication Tips For Senior Software Engineers

YouTube player

Like the video above, this one is similar but… through a different lens! Senior software engineers — you can make a big difference by focusing on how you deliver feedback! Consider the two approaches in this video and see if they can make a difference for you!

How Can I Write Code On My Phone? – Unlock Potential For Mobile-First Coders

How Can I Write Code On My Phone? - Unlock Potential For Mobile-First Coders

While this isn’t something I see across all social media channels, there are some spots where I see this come up every single day. Multiple times a day. It comes up a SHOCKING amount for some particular audiences. I wanted to make sure I could at least start to socialize this and get it on my radar going forward.

How To Make A Budgeting App In C# With Blazor

How To Make A Budgeting App In C# With Blazor

Building things is an excellent way to learn! So if you’re interested in diving into Blazor, here’s an example for you. Of course, when you’re done, you can go write solid unit tests for it based on everything you learned in this newsletter!

Blazor Unit Testing Best Practices – How to Master Them for Development Success

Blazor Unit Testing Best Practices - How to Master Them for Development Success

Full transparency – this article was my first big failure in a long time. Not because I think the article itself is crap, but because I failed to deliver on my original goal. I wanted to publish this article here along with the content at the beginning of this newsletter (AND the videos AND additional articles), but it never lined up that way with my vacation. As a result, this was meant to be a primer article and there was nothing else to go along with it. I’m disappointed in myself, but I tried to add some extra details afterward. I’ll continue to build out my Blazor testing content to help round all of this out.

How to Implement the Facade Pattern in C# for Simplified Code and Increased Efficiency

How to Implement the Facade Pattern in C# for Simplified Code and Increased Efficiency

The Facade design pattern is one of my favorites. I’ve created videos and other articles about it, and it’s something I use all of the time in my own development. Head on over to read more about one of my favorites!

How to Balance Technical Debt – Tackle It Before It Tackles You

How to Balance Technical Debt - Tackle It Before It Tackles You

Tech debt is inescapable! Embrace it! That’s going to mean that we need to understand how we can properly manage tech debt and schedule it properly with everything else we need to do. Check out this article for some thoughts on how to balance tech debt.


C# Snippet of the Week:

// Exploring C# 9's Record Type
public record Car(
  string Make,
  string Model);

var myCar = new Car("Toyota", "Camry");
var yourCar = myCar with { Model = "Corolla" };

// Outputs: Toyota
Console.WriteLine(yourCar.Make);

// Outputs: Corolla
Console.WriteLine(yourCar.Model);

The Record Type in C# 9 offers immutability and value-based equality in a more concise syntax. In the snippet above, the with keyword allows for non-destructive mutation, creating a new instance (yourCar) while preserving the original (myCar).

I’ve written about these before and even explained how you can access record types in earlier versions of .NET! Originally when I first heard about records I didn’t see a lot of value. However, the built-in equality comparisons and ToString() implementations have made these a game changer for DTOs.


Insightful Tip:

Blazor Server vs WebAssembly:

Did you know: Blazor Server and Blazor WebAssembly are two distinct hosting models for Blazor applications? While Blazor Server runs on the server and sends UI updates over a real-time connection, Blazor WebAssembly runs directly in the browser using WebAssembly. Choose the model that best fits your application’s needs.

Sounds like I have some more content to create!


Learning Resource of the Week:

AWS Services for C# Developers​ – Dometrain

Nick Chapsas adds another gem to the Dometrain list of courses covering how to use AWS with C#. You’re reading my newsletter so there’s no doubt in my mind you’ve heard of Nick and his awesome content. This course is by him, personally, so you know the quality will be there.

And guess what?

IT’S FREE! What are you waiting for?!


Solution to Last Week’s Coding Challenge:

Did you try out last week’s coding challenge? This is a pretty common variation of a type of problem that you could get on an interview! You can find a working solution here that can be run right in the browser.


This Week’s Coding Challenge:

Build a basic Binary Search Tree (BST) in C#!

The BST should support insertion, searching for a value, and in-order traversal.

public class TreeNode
{
    public int Value;
    public TreeNode Left;
    public TreeNode Right;
}

public class BinarySearchTree
{
    // Implement your BST methods here
}

Some flexibility with the public API this week! Try it out and feel free to share what you come up with!


Affiliations:

These are products & services that I trust, use, and love. I get a kickback if you decide to use my links. There’s no pressure, but I only promote things that I like to use!

      • RackNerd: Cheap VPS hosting options that I love for low-resource usage!
      • Contabo: Alternative VPS hosting options with very affordable prices!
      • ConvertKit: This is the platform that I use for my newsletter!
      • SparkLoop: This service helps me add different value to my newsletter!
      • Opus Clip: This is what I use for help creating my short-form videos!
      • Newegg: For all sorts of computer components!
      • Bulk Supplements: For an enormous selection of health supplements!
      • Quora: I try to answer questions on Quora when folks request them of me!


    Final Thoughts:

    That’s it for this week’s edition of Dev Leader Weekly! I hope you found something valuable in our short time together. Remember, consistency is key to success in software engineering, so keep learning, keep coding, and never stop improving.

    If you have any questions or topics you’d like me to cover, feel free to reply to this email or reach out to me on social media. I strive for all of my content to be as focused on topics that YOU want to hear. My goal is to help you become a better software engineer, and that means addressing your curiosities and questions.

    Until next time, happy coding!

    Nick “Dev Leader” Cosentino
    [email protected]

    Socials:
    Blog
    Dev Leader YouTube
    Follow on LinkedIn
    Dev Leader Instagram

    P.S. If you enjoyed this newsletter, consider sharing it with your fellow developers. Let’s grow our community together!

    author avatar
    Nick Cosentino Principal Software Engineering Manager
    Principal Software Engineering Manager at Microsoft. Views are my own.