© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2022
P. HimschootMicrosoft Blazorhttps://doi.org/10.1007/978-1-4842-7845-1_8

8. Unit Testing

Peter Himschoot1  
(1)
Melle, Belgium
 

In previous chapters, we have been building a PizzaPlace application, which contains a number of components. We have tested these components by running the application and interacting with it. Here, we will look at writing unit tests for Blazor components using bUnit and MOQ.

Where Can We Find Bugs?

When building software, what are the causes of bugs? As it turns out, in every step of building software, bugs can be introduced. So let us walk over the lifetime of a software project as shown in Figure 8-1. Figure 8-1 also illustrates another obvious fact: the sooner you can find a bug, the cheaper it is to fix it.
../images/469993_3_En_8_Chapter/469993_3_En_8_Fig1_HTML.jpg
Figure 8-1

The Cost of Fixing Bugs

Requirements

Sometimes bugs are introduced even before a single line of code is written. Writing good requirements is hard, because these need to explain business concepts to developers who are typically not well versed in this domain. The same thing counts for advanced engineering concepts, like rocket building. Let’s look at an example. NASA lost its $125,000,000 Mars Climate Orbiter because of a simple missing piece of information in the requirements: which units to use. One team was using metric units (meters, kilogram), while another team was using imperial units (inch, pounds), and there was no conversion in place because each team thought the other team was using the same units! You can read more about it here: www.latimes.com/archives/la-xpm-1999-oct-01-mn-17288-story.html.

This bug could easily be averted! Since the specifications never mentioned the units, if someone had asked which units to use and add this to the specifications, this Mars Climate Orbiter would probably be spinning around Mars right now! So as a developer, if you think something is confusing or ambiguous, ask! Never assume anything!

Coding

It is easy to introduce bugs during coding. That is part of developing software. Code that was working very well can become buggy by a benign change. So how can we discover these bugs? We should automate our testing. By writing a piece of code (a unit test) that checks if another piece of code is behaving as expected, we can run this test every day and discover the bug hours after it was written. Have you ever received a bug report about some functionality you wrote three months ago? You will probably rub your head wondering what you were trying to do (and you write this code!). After studying your code for a while, understanding kicks in and you fix the bug (let us say a simple one-off bug). Now imagine the bug is discovered an hour after you made the change. Do you need to figure out what your code was doing? No, it is still fresh in your memory. That makes unit testing so efficient. And when you finish your code and all your unit tests are green (meaning check out OK), you can go home and sleep soundly!

Same thing counts for refactoring code. I mean, cleaning up some code and keeping the same functionality. How do you know if you introduced bugs while refactoring when there are no unit tests? If there are unit tests and they all pass before refactoring, it is easy to see after refactoring if you broke something. Just run the unit tests, and if they all pass, you did not break anything that was known to be working! Again, you can go home and sleep knowing you made your code more maintainable and did not introduce any new bugs!

Integration

Your code works on your machine. And your colleague’s code also works on their machine. But will your code work together with your colleague’s code? That is what integration is all about. A long time ago, teams would integrate code from different teams at the end of the project. Guess what!? This never went well, resulting in project overruns, sometimes by months. So development teams started to integrate at the end of each month and then at the end of the week. Integrations started to become automated using build systems. Now we can do continuous integration where we integrate changes to the code after each commit in source code. And when we have unit tests, we can run these after the compilation ends and use that to catch breaking changes. Again, this should illustrate the role of good unit tests. You use them to see if your code is working and also to see if everyone’s code keeps on working.

Beta Testing

At a certain point in time, you should expose end users to your application. Why? Because developers are not normal people. End users want things to be as simple as possible. For example, look at google.com. This site only has one text box where you type your question and a button to do the search. This simplicity made Google the most used search engine (sorry Microsoft). Developers are control freaks; they want power, not simplicity! Just open the options screen in Visual Studio. You can tweak just about anything! And most end users are not as proficient using computers as developers, sometimes resulting in surprises. Let me tell you about a personal experience I once had. We had built software that runs in a factory, and on a Sunday (they work in that factory continuously), I get a call from an end user. He told me “The button does not work!”. After half an hour on the phone, I decided to drive over there and see for myself. I get there and I click the button, and it works! So what was the problem? The button was small because we needed to cram a lot on the screen, and the end user has a bit of a tremor and moved the mouse when clicking, resulting in a click outside of the button. So we fixed the bug by making the button bigger. This is a nice example of a usability problem, where we as developers are not always aware of. So expose your software to your users often, and gain their feedback. We are building it for our end users, right?

Post-release

Perfect software does not exist. Have you ever writing an application that is bug-free? No? I have! It is called “Hello World!”. Anything beyond that is impossible. But having an end user discover a bug is bad news. It will lower the trust in the development team and in the quality of the software. So how do we stop bugs from making it into production? You can’t! The only thing you can do is to test as much as possible and warn the users that they may encounter bugs, especially early after release.

Why Should We Use Unit Tests?

So how do you test your Blazor application? Hit run and interact with the UI? No problem there, except every time you make a change to your application, you should test everything again. Who has time for that? Can’t someone else do it? Yes, that machine in front of you can! With unit testing, we automate this unit testing process.

What Makes a Good Unit Test?

When you practice unit testing, your development life cycle looks like this: make some change to the code, build, fix compiler errors, build again, and then run all your tests. Then fix the bugs discovered by your tests. And then you start again. How long does building your application take? A couple of seconds? Now imagine that your unit tests take 5 minutes. Would you want to wait for that? Would you be tempted to disable running the unit tests? A good unit test should be fast so we don’t have to wait very long. What makes unit tests slow? Typically, this is caused by accessing slow resources, like databases, disks, and the network. So with a unit test, we will avoid using slow resources. What if your unit tests need some setup? Every time you need to run the tests, you would have to prepare some things manually. Again, we don’t have time for that. A good unit test should also be automatic and repeatable , meaning that the test should report on success or failure and that again we avoid things that need some manual setup. What could that be? Again databases, files on disk, and the network! Another aspect of a good unit test is consistency . If your unit test fails, this should be because of a bug in your code, not because someone tripped over a network cable making the database or network inaccessible! So again, we should avoid things like databases, file shares, and networks.

Tests which do not have all the aspects from earlier do exist. You will have to write a test to interact with the database (but don’t start testing the framework used to access the database; that is the framework’s author’s job!). Their tests are known as integration tests , because we will run them during the build, not during the development life cycle.

Unit Testing Blazor Components

Let us create a couple of tests for a Blazor application. In the code download for this chapter, you can find the testing solution. Open it with your favorite editor. Everything in the project should look familiar. There is the Counter component and the FetchData component which uses an IWeatherService to retrieve the weather forecasts from a server.

Adding a Unit Test Project

Let us look at an example using xUnit , which is a popular testing library for .NET which we will also use for testing our Blazor components.

When you are using Visual Studio, right-click the test folder and select Add new project. In the Add New Project dialog, search for the xUnit Test Project template. Now click Next. Set the Location to the test folder and name it Testing.ComponentTests.

If you are using Code, open the command prompt to the test folder and execute the following command:
dotnet new xunit -n Testing.ComponentTests
Now change the directory to the parent directory and execute
dotnet sln add .\test\Testing.ComponentTests
No matter which tool you are using, add project references to the client and the Shared project. The test project file should look like Listing 8-1, where I also enabled nullable reference types. Since I will test components that have nullable reference types enabled, I think the unit test project should too.
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    <Nullable>enable</Nullable>
    <IsPackable>false</IsPackable>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
    <PackageReference Include="xunit" Version="2.4.1" />
    <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
      <PrivateAssets>all</PrivateAssets>
    </PackageReference>
    <PackageReference Include="coverlet.collector" Version="1.3.0">
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
      <PrivateAssets>all</PrivateAssets>
    </PackageReference>
  </ItemGroup>
  <ItemGroup>
    <ProjectReference Include="..\..\src\Client\Testing.Client.csproj" />
    <ProjectReference Include="..\..\src\Shared\Testing.Shared.csproj" />
  </ItemGroup>
</Project>
Listing 8-1

The Testing.ComponentTests Project

Adding bUnit to the Test Project

With the current unit test project, we can test our services and other non-Blazor classes. In order to test Blazor components, we need to add bUnit . So use your favorite method to add the bUnit package (choose latest stable version).

You also need to change the SDK for your project as in Listing 8-2. We need to do this because we will use razor syntax to build unit tests for Blazor components.
<Project Sdk="Microsoft.NET.Sdk.Razor">
Listing 8-2

bUnit Projects

Write Your First Unit Test

Now that we have everything in place, we can write our first unit test. We will start by writing a simple unit test and see how this works with Visual Studio and Code.

Writing Good Unit Test Methods

Every unit test will consist of three phases: Arrange, Act, and Assert, also known as the triple A of unit testing . The Arrange phase will set up the unit test by creating the subject under test (SUT), by which I mean the class we want to test and its dependencies. The Act phase will perform the call on the method we want to test, and the Assert will verify if the outcome is successful.

Add a new class called Utils to the Shared project as in Listing 8-3. The Square method should return the square of a number (and it has a bug).
namespace Testing.Shared
{
  public class Utils
  {
    public int Square(int i)
    {
      return i;
    }
  }
}
Listing 8-3

A Simple Utils Class

Let us write a simple unit test for this method, as in Listing 8-4. With xUnit, a unit test is a public method with the [Fact] attribute on it. As this attribute says, the result of the test should be a fact! In the Arrange phase, we set up the subject under test which I like to call sut. This way, it is easy for me to identify the instance that I want to test (just a convention, name it as you like). Then in the Arrange phase, we call the Square method, storing the result in the actual variable. Next comes the Assert phase, where I am using the Assert class from xUnit, to verify if the result matches the expected result. The Assert class has a whole range of methods to check if the outcome of the test is the expected outcome. Here, we are using the Equals method to see if the outcome equals 9, which should be the square of 3.
using Testing.Shared;
using Xunit;
namespace Testing.ComponentTests
{
  public class SquareShould
  {
    [Fact]
    public void Return9For3()
    {
      // Arrange
      var sut = new Utils();
      // Act
      var actual = sut.Square(3);
      // Assert
      Assert.Equal(expected: 9, actual: actual);
    }
  }
}
Listing 8-4

Testing the Square Method

Running Your Tests

With Visual Studio, open the Test Explorer window (Test ➤ Test Explorer) as in Figure 8-2. With Visual Studio, Test Explorer is the place to run unit tests and review the results. After opening Test Explorer, it will scan your solution for unit tests and list them. Now click the left green arrow in this window to run all your tests.
../images/469993_3_En_8_Chapter/469993_3_En_8_Fig2_HTML.jpg
Figure 8-2

The Test Explorer

The test will run and fail as shown in Figure 8-3.
../images/469993_3_En_8_Chapter/469993_3_En_8_Fig3_HTML.jpg
Figure 8-3

The Test Fails

You can also run unit tests from Visual Studio Code, but you will have to install the .NET Core Test Explorer extension as shown in Figure 8-4.
../images/469993_3_En_8_Chapter/469993_3_En_8_Fig4_HTML.jpg
Figure 8-4

The .NET Core Test Explorer Extension

Now you can run your tests by clicking the Test Explorer icon in the left side of VSC as shown in Figure 8-5.
../images/469993_3_En_8_Chapter/469993_3_En_8_Fig5_HTML.jpg
Figure 8-5

Test Explorer in VSC

VSC Test Explorer will display a couple of buttons as shown in Figure 8-6. From left to right, you have a button to run the tests, refresh the list of available tests, stop test execution, and show the log with the test results.
../images/469993_3_En_8_Chapter/469993_3_En_8_Fig6_HTML.jpg
Figure 8-6

The VSC Test Explorer Controls

Click the refresh button. This will scan your project for available tests. Should Test Explorer fail to find any tests, you can set the dotnet-test-explorer.testProjectPath setting, for example:
"dotnet-test-explorer.testProjectPath": "**/*Tests.csproj"
Click the play icon to run the test. The test will run and fail. To see the result of the test, click the log button and then check out the PROBLEMS tab as shown in Figure 8-7.
../images/469993_3_En_8_Chapter/469993_3_En_8_Fig7_HTML.jpg
Figure 8-7

Reviewing Failed Tests

Making Your Test Pass

Why did the test fail? If you put a breakpoint in the Square method and click the arrow in Test Explorer again, you will see that Visual Studio does not stop on the breakpoint. Same thing for VSC. Why? Debugging needs some special setup, and this takes time. Remember that we want our tests to complete as short as possible? With Visual Studio, you can enable the debugger as follows. Right-click the test in the Test Explorer window and select Debug (similar in VSC). Now the debugger will stop on your breakpoint. When you step in the Square method, you should see the bug (duh!). Fix it as in Listing 8-5.
namespace Testing.Shared
{
  public class Utils
  {
    public int Square(int i)
    {
      return i*i;
    }
  }
}
Listing 8-5

The Corrected Square Method

Now run the test again (with or without the debugger). Now it should pass as in Figures 8-8 and 8-9.
../images/469993_3_En_8_Chapter/469993_3_En_8_Fig8_HTML.png
Figure 8-8

The Test Passes in VS

../images/469993_3_En_8_Chapter/469993_3_En_8_Fig9_HTML.jpg
Figure 8-9

The Test Passes in VSC

Using Facts and Theories

But what about other values? With xUnit, we can write a whole series of tests without having to copy-paste a ton of them (copy-pasting to duplicate code is generally bad, also known as Don’t Repeat Yourself (DRY)). Add another unit test to the SquareShould class as in Listing 8-6. Here, we are using the [Theory] attribute to tell xUnit to run this with different arguments. And we use the [InlineData] attribute to pass the arguments to the test method.
[Theory]
[InlineData(1,1)]
[InlineData(2,4)]
[InlineData(-1,1)]
public void ReturnSquareOfNumber(int number, int square)
{
  // Arrange
  var sut = new Utils();
  // Act
  var actual = sut.Square(number);
  // Assert
  Assert.Equal(expected: square, actual: actual);
}
Listing 8-6

Using Theories

Now when we run our tests, you will see in Figures 8-10 and 8-11 that xUnit runs three tests, one for each [InlineData] attribute.
../images/469993_3_En_8_Chapter/469993_3_En_8_Fig10_HTML.png
Figure 8-10

VS Test Results with Theories

../images/469993_3_En_8_Chapter/469993_3_En_8_Fig11_HTML.jpg
Figure 8-11

VSC Test Results with Theories

Checking Your Sanity

Have you ever had a piece of code that did things differently than what you expected? Personally, I start to doubt my sanity then, like “Am I going crazy?” Or have you used someone’s method that was badly documented and did not do as it should? With unit testing, you can set up checks to see if a method does what you think it should do. And if it does not, maybe you need to talk to the author and see what makes more sense. When you have a unit test, you can attach it to a bug report, making it easy for the author to reproduce the bug. Let us look at an example again. Now I want to see if the Square method throws an error when we pass a big integer to it (and not every squared integer is another integer because it is limited in range). Add another test method like in Listing 8-7. So here we call Square with the largest int possible. The result can never fit into an int, so we expect this to throw an OverflowException.
[Fact]
public void ThrowOverflowForBigNumbers()
{
  // Arrange
  var sut = new Utils();
  // Act & Assert
  Assert.Throws<OverflowException>(() =>
  {
    int result = sut.Square(int.MaxValue);
  });
}
Listing 8-7

Testing Exceptional Cases

But when we run, the test fails as in Figure 8-12.
../images/469993_3_En_8_Chapter/469993_3_En_8_Fig12_HTML.png
Figure 8-12

Sanity Check Please?

Why does this fail? Let us put a breakpoint on the Square method. Maybe we are doing something wrong here? Run the test with the debugger. When the debugger stops, look at the value of the argument: 2147483647. This is the largest signed int. Now step out of the method until after the result is set. What is its value? It is 1. Now 2147483647*2147483647 is not 1! So again, what is happening? It turns out that C# works like C++ and C. These programming languages do not throw exceptions by default when a calculation overflows! They even use this to create hashing and encryption algorithms. So how can we fix this? You can turn on overflow checking using the C# checked keyword as in Listing 8-8.
namespace Testing.Shared
{
  public class Utils
  {
    public int Square(int i)
    {
      checked
      {
        return i * i;
      }
    }
  }
}
Listing 8-8

Enabling Overflow Checking

Run your test again. Now it passes. Whew! This was actually normal behavior.

Unit testing is great to discover these weird behaviors and allows you to catch modifications that cause bugs later.

Write a bUnit Tests with C#

We have seen how we can write unit tests for .NET classes and their methods. Here, we will look at how we can write tests for Blazor components on top of xUnit.

Note

All tests written here use xUnit, but you can also use NUnit or MSTest. All of these are test frameworks that apply the same principles. You can even mix these frameworks so you don’t have to rewrite old tests when moving to another test framework.

Understanding bUnit?

bUnit is a testing library for Blazor components, written by Egil Hansen, and the sources can be found in GitHub at https://github.com/bUnit-dev/bUnit. With bUnit, you can easily write unit tests for Blazor components. Why should we write unit tests for Blazor components? Same reason you write unit tests for regular classes: to ensure they work as expected and that they keep on working in case some dependency gets updated. Of course, most of your testing should be on the service classes that implement business logic. For example, you want to make sure your Blazor component calls a certain method on a service when the user interacts with that component. With bUnit, we can automate that so no user has to actually click a button! And we can run these tests continuously so we will know when we break a component minutes after the change.

Part of testing a Blazor component is to render and examine the output of a component. But it goes way beyond this. You can interact with the component and see the changes, replace dependencies, etc.

Let us start with the Counter component , as in Listing 8-9. This now familiar component displays a currentCount field which is initially 0. So a very simple unit test would be to see if the component’s output matches the expected output.
@page "/counter"
<h1>Counter</h1>
<p>Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">
  Click me
</button>
@code {
    private int currentCount = 0;
    private void IncrementCount()
    {
        currentCount++;
    }
}
Listing 8-9

The Counter Component

Add a new class called CounterShould to the unit test project. You can name this class anything you want, but I like the naming convention where I use the method or component name and then the word “Should”. Derive this class from the TestContext base class, which will give you access to all the handy methods from bUnit. We will be using these methods as we go along, and by deriving your test class from TestContext, they become available through inheritance. Implement the first unit test RenderCorrectlyWithInitialZero as in Listing 8-10.
using Bunit;
using Testing.Client.Pages;
using Xunit;
namespace Testing.ComponentTests
{
  public class CounterShould : TestContext
  {
    [Fact]
    public void RenderCorrectlyWithInitialZero()
    {
      var cut = RenderComponent<Counter>();
      Assert.Equal(@"
        <h1>Counter</h1>
        <p>Current count: 0</p>
        <button class=""btn btn-primary"" >
          Click me
        </button>
        ", cut.Markup);
    }
  }
}
Listing 8-10

The CounterShould Class

Here, we are using xUnit together with bUnit, so our unit test has the [Fact] attribute. First, we do the Arrange phase, where we create the component under test (which I name cut, similar to sut) by calling the RenderComponent<Counter> method. This will create the component and render it in one go. So this also takes care of the Act phase. Next, we do the Assert phase, where we want to see if the component generated the right kind of output.

This test will fail. Why? Just run the test, and look at the test output as in Figure 8-13. Look again at the Assert statement in Listing 8-10. Here, we expect the markup of our component to match the literal string. And it does match in a way, except for whitespace and newlines. We could now do the work and update our string to the real output, but this is too sensitive to little changes we might make later to our component.
../images/469993_3_En_8_Chapter/469993_3_En_8_Fig13_HTML.jpg
Figure 8-13

Our Test Fails

Let us improve the test as in Listing 8-11. Now we are using the MarkupMatches method , which will perform a semantic comparison between the component’s markup and our string. This will ignore whitespace, newlines, comments, and other irrelevant things during the comparison, and now we should see the test pass! Now our test will no longer break when we add a newline or a comment in the component that changes the markup’s formatting!
using Bunit;
using Testing.Client.Pages;
using Xunit;
namespace Testing.ComponentTests
{
  public class CounterShould : TestContext
  {
    [Fact]
    public void RenderCorrectlyWithInitialZero()
    {
      var cut = RenderComponent<Counter>();
      cut.MarkupMatches(@"
        <h1>Counter</h1>
        <p>Current count: 0</p>
        <button class=""btn btn-primary"" >
          Click me
        </button>
        ");
    }
  }
}
Listing 8-11

Improving Our Unit Test with Semantic Comparison

We can even do better and focus on the relevant part of the component. We know that our Counter component uses a <p> element to render the currentCount variable, but how do we access this part of the render tree? The bUnit library has a Find method that takes a CSS selector and returns the result of the query. Add another test method to the ShouldRender class as in Listing 8-12. We Find the <p> element, and we can see if it matches the expected output using the MarkupMatches method , which ignores whitespace.
[Fact]
public void RenderParagraphCorrectlyWithInitialZero()
{
  var cut = RenderComponent<Counter>();
  cut.Find(cssSelector: "p")
     .MarkupMatches("<p>Current count: 0</p>");
}
Listing 8-12

Using the Find Method

Run your tests and see if they pass, which they should.

What happens when the test fails?

In the RenderParagraphCorrectlyWithInitialZero method , replace the 0 with a 1. Run the test. It fails! Select the test and you should see the following output as in Figure 8-14. This output shows us what is wrong, and now we can change the component (or the test) until the test passes. Fix the test.
../images/469993_3_En_8_Chapter/469993_3_En_8_Fig14_HTML.jpg
Figure 8-14

Our bUnit Test Fails

Testing Component Interaction

Our Counter component has a button, and when you click the button, it should increment the currentCount by 1 and render the new value. Let us look at how we can perform a test on a Blazor component by interacting with it and see if the component was updated correctly. Add a new unit test to the ShouldRender class as in Listing 8-13. The second line in the test uses the Find method to retrieve the button and then uses the Click method to perform the @onclick event on it. This should have the expected side effect, which we test on the next line to see if the component re-rendered with the expected value. Run the test, which should pass. Hey, this was easy!
[Fact]
public void IncrementCounterWhenButtonIsClicked()
{
  var cut = RenderComponent<Counter>();
  cut.Find(cssSelector: "button")
     .Click();
  cut.Find(cssSelector: "p")
     .MarkupMatches(@"<p>Current count: 1</p>");
}
Listing 8-13

Interacting with the Counter Component

The bUnit library comes with many dispatch methods that make it possible to trigger events on your component. Retrieve the element in the component using the Find method, and then call the appropriate dispatch method on it, for example, Click. These dispatch methods also allow you to pass event arguments. So let us look at an example.

Start by adding a new component to your Blazor project called MouseTracker with markup from Listing 8-14.
<div style="width: 300px; height: 300px;
            background: green; margin:50px"
     @onmousemove="MouseMove">
  @pos
</div>
Listing 8-14

The MouseTracker Component’s Markup

This component has a MouseMove event handler as shown in Listing 8-15.
using Microsoft.AspNetCore.Components.Web;
namespace Testing.Client.Pages
{
  public partial class MouseTracker
  {
    private string pos = "";
    private void MouseMove(MouseEventArgs e)
      => pos = $"Mouse at {e.ClientX}x{e.ClientY}";
  }
}
Listing 8-15

The MouseTracker Component’s Code

In the unit test project, add a new class called MouseTrackerShould with a single unit test as in Listing 8-16. During the Arrange phase of the bUnit test, we create an instance of MouseEventArgs with ClientX and ClientY set to some value. We then create an instance of the MouseTracker component using the TestContext’s RenderComponent method. Now we Find the div from the component and store it in the theDiv reference.

Now we can perform the Act phase of the test by triggering the MouseMove event, passing the MouseMoveEventArgs instance we created before. This will re-render the component, so we are ready for the Assert phase where we check if the theDiv has the expected content using the MarkupMatches method. Do note that we use semantic comparison again, and here we can tell the compare to also ignore the style attribute using the style:ignore attribute. We will talk more about this in a later section of this chapter.
using Bunit;
using Microsoft.AspNetCore.Components.Web;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using Testing.Client.Pages;
using Xunit;
namespace Testing.ComponentTests
{
  public class MouseTrackerShould : TestContext
  {
    [Fact]
    public void ShowCorrectMousePosition()
    {
      var eventArgs = new MouseEventArgs()
      {
        ClientX = 100,
        ClientY = 200
      };
      var cut = RenderComponent<MouseTracker>();
      var theDiv = cut.Find(cssSelector: "div");
      theDiv.MouseMove(eventArgs);
      theDiv.MarkupMatches($"<div style:ignore>Mouse at {eventArgs.ClientX}x{eventArgs.ClientY}");
    }
  }
}
Listing 8-16

The MouseTrackerShould Unit Test

Run the test; it should pass.

Passing Parameters to Our Component

With data binding, we can pass parameters from the parent component to a child component. How do we pass parameters with bUnit? Start by copying the Counter component in the Blazor project, rename it to TwoWayCounter , and change it to look like Listing 8-17. This TwoWayCounter component has a couple of parameters, including the CurrentCount and the Increment parameter.
<h1>Counter</h1>
<p>Current count: @CurrentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
    private int currentCount = 0;
    [Parameter]
    public int CurrentCount
    {
        get => currentCount;
        set
        {
            if (value != currentCount)
            {
                currentCount = value;
                CurrentCountChanged.InvokeAsync(currentCount);
            }
        }
    }
    private int increment = 1;
    [Parameter]
    public int Increment {
        get => increment;
        set
        {
            if( value != increment)
            {
                increment = value;
                IncrementChanged.InvokeAsync(increment);
            }
        }
    }
    [Parameter]
    public EventCallback<int> CurrentCountChanged { get; set; }
    [Parameter]
    public EventCallback<int> IncrementChanged { get; set; }
    private void IncrementCount()
    {
        CurrentCount+=Increment;
    }
}
Listing 8-17

The TwoWayCounter Component

Add another unit test to the test project called TwoWayCounterShould , and add the first bUnit test as in Listing 8-18. We want to pass two parameters to this component, and we can do this by using an overload of the RenderComponent method as shown in Listing 8-18. This takes a delegate which has a parameters argument of type ComponentParameterCollectionBuilder<TComponent>. This class has an Add method with two arguments: expression where you pass the name of the parameter and the value for the parameter.
using Bunit;
using Testing.Client.Pages;
using Xunit;
namespace Testing.ComponentTests
{
  public class TwoWayCounterShould : TestContext
  {
    [Fact]
    public void IncrementCounterWhenClicked()
    {
      var cut = RenderComponent<TwoWayCounter>(
        parameters =>
            parameters.Add(counter => counter.CurrentCount, 0)
                      .Add(counter => counter.Increment, 1)
          );
      cut.Find("button").Click();
      cut.Find("p")
         .MarkupMatches("<p>Current count: 1</p>");
    }
  }
}
Listing 8-18

The TwoWayCounterShould Test Class

This way of passing parameters to a component is very convenient, since we can use IntelliSense to choose the parameter’s name. There are other ways to pass the parameters, as shown in Listing 8-19. Here, we use xUnit’s Theory to pass different parameters to the component, and each parameter is passed as a ValueTuple, containing the name and value of each parameter (that is why these are wrapped in an opening and closing parenthesis).

However, I personally don’t like this way of working, because now we are passing the argument’s name as a string. The compiler will not check the contents of a string to see if it is actually the name of a parameter. What happens when you make a mistake (or you decide later to rename the parameter)? The compiler will not complain, and you will get a failing test with the following message:
Message:
    System.InvalidOperationException : Object of type 'Testing.Client.Pages.TwoWayCounter' does not have a property matching the name 'CurrentCuont'.
Using hard-coded strings in code that contain names of classes, properties, and other code constructs is an anti-pattern which I call “string-based programming” and should be avoided.
[Theory]
[InlineData(3)]
[InlineData(-3)]
public void IncrementCounterWithIncrementWhenClicked(int increment)
{
  var cut = RenderComponent<TwoWayCounter>(
        ("CurrentCount", 0),
        ("Increment", increment)
      );
  cut.Find("button").Click();
  cut.Find("p")
     .MarkupMatches($"<p>Current count: {increment}</p>");
}
Listing 8-19

Using a Theory to Test Different Cases

Of course, with modern C#, we can fix this and still use this style as in Listing 8-20. Here, we use the nameof operator, which takes the name of a property and returns the string representation of that property. You can also use nameof with classes, methods, and other things.
[Theory]
[InlineData(3)]
[InlineData(-3)]
public void IncrementCounterWithIncrementWhenClickedWithNameOf(
  int increment)
{
  var cut = RenderComponent<TwoWayCounter>(
        (nameof(TwoWayCounter.CurrentCount), 0),
        (nameof(TwoWayCounter.Increment), increment)
      );
  cut.Find("button").Click();
  cut.Find("p")
     .MarkupMatches($"<p>Current count: {increment}</p>");
}
Listing 8-20

Using nameof to Pass Property Names

Testing Two-Way Data Binding and Events

Our TwoWayCounter has parameters to implement two-way data binding. Let us see if this component implements this correctly. We can use the same technique as before to pass handlers to the CurrentCountChanged and IncrementChanged parameters. But before we do this, add the FluentAssertions package to your test project. FluentAssertions allows you to write your assert statements in a more readable and concise way, and we will use it here (although this is not required). You can find out more about fluent assertions at https://fluentassertions.com.

Look at the bUnit test from Listing 8-21. We are adding four parameters, where two of them are of type EventCallback<int>. We assign a value to the EventCallback<int> using a delegate, and this delegate increments a local variable. This way, we count the number of invocations of the CurrentCountChanged and IncrementChanged event callback.

Note

You can also use this technique to test regular delegates like Action and Func.

After clicking the button, we expect the CurrentCountChanged to have been invoked, and we test this using the FluentAssertions Should().Be(1) method call. But we also want to test the Increment property’s changed handler, and we can do this by accessing the component using the cut.Instance property and directly assigning a new value to Increment. Should your compiler issue a warning on this statement, that is normal because you are normally not allowed to access a component’s parameters directly from code.
[Fact]
public void TriggerChangedEventForCurrentCounter()
{
  int nrOfCurrentCountChanged = 0;
  int nrOfIncrementChanged = 0;
  var cut = RenderComponent<TwoWayCounter>(parameters =>
        parameters.Add(counter => counter.CurrentCount, 0)
                  .Add(counter => counter.Increment, 1)
                  .Add(counter => counter.CurrentCountChanged,
                    () => nrOfCurrentCountChanged++)
                  .Add(counter => counter.IncrementChanged,
                    () => nrOfIncrementChanged++)
                  );
  cut.Find("button").Click();
  cut.Instance.Increment = 2;
  nrOfCurrentCountChanged.Should().Be(1);
  nrOfIncrementChanged.Should().Be(1);
}
Listing 8-21

Testing Two-Way Changed Handlers

You can also change a parameter value after the first render of your component. Look for an example in Listing 8-22, where we use the SetParametersAndRender method to modify the value of the Increment parameter.
[Fact]
public void TriggerChangedEventForCurrentCounter2()
{
  int nrOfIncrementChanged = 0;
  var cut = RenderComponent<TwoWayCounter>(parameters =>
        parameters.Add(counter => counter.CurrentCount, 0)
                  .Add(counter => counter.Increment, 1)
                  .Add(counter => counter.IncrementChanged,
                    () => nrOfIncrementChanged++)
                  );
  cut.SetParametersAndRender(parameters =>
    parameters.Add(counter => counter.Increment, 2));
  nrOfIncrementChanged.Should().Be(1);
}
Listing 8-22

Modifying the Value of a Parameter

Testing Components that Use RenderFragment

What about components that use RenderFragment such as ChildContent and templated components? RenderFragment is a special Blazor type, so it needs some special care. Start by adding an Alert component to your Blazor project, such as Listing 8-23.
<div class="alert alert-secondary mt-4" role="alert">
  @ChildContent
</div>
@code {
  [Parameter]
  public RenderFragment ChildContent { get; set; } = default!;
}
Listing 8-23

The Alert Component

Now add the AlertShould class from Listing 8-24 to your test project. As you can see, the ChildContent is just another parameter but comes with some convenience methods to make it easy to add.
using Bunit;
using Testing.Client.Pages;
using Xunit;
namespace Testing.ComponentTests
{
  public class AlertShould : TestContext
  {
    [Fact]
    public void RenderSimpleChildContent()
    {
      var cut = RenderComponent<Alert>(parameters =>
      parameters.AddChildContent("<p>Hello world!</p>"));
      cut.MarkupMatches(@"
        <div class=""alert alert-secondary mt-4"" role=""alert"">
            <p>Hello world!</p>
        </ div >
      ");
    }
  }
}
Listing 8-24

The AlertShould Test Class

Should the Alert component have additional parameters, we can pass the just like in Listing 8-18.

In Listing 8-24, we pass some simple HTML as the ChildContent, but we can do more complex things. For example, in Listing 8-25, we pass the Counter as the ChildContent .
[Fact]
public void RenderCounterAsChildContent()
{
  var cut = RenderComponent<Alert>(parameters =>
    parameters.AddChildContent<Counter>());
  var p = cut.Find("p");
  p.MarkupMatches("<p>Current count: 0</p>");
}
Listing 8-25

Passing a Counter As ChildContent

We can even pass parameters to the ChildContent, for example, when using the TwoWayCounter as in Listing 8-26.
[Fact]
public void RenderTwoWayCounterWithParametersAsChildContent()
{
  var cut = RenderComponent<Alert>(parameters =>
    parameters.AddChildContent<TwoWayCounter>(parameters =>
    parameters.Add(counter=>counter.CurrentCount, 3)));
  var p = cut.Find("p");
  p.MarkupMatches("<p>Current count: 3</p>");
}
Listing 8-26

Passing the TwoWayCounter As ChildContent

You can even call AddChildContent multiple times to add more than one fragment. Listing 8-27 illustrates this where we add both an HTML string and a Counter. Also note the use of a const string so we don’t need to sync the content used in the AddChildContent and MarkupMatches methods (Don’t Repeat Yourself Principle (DRY) ).
[Fact]
public void RenderTitleAndCounterAsChildContent()
{
  const string header = "<h1>This is a counter</h1>";
  var cut = RenderComponent<Alert>(parameters =>
    parameters.AddChildContent(header)
              .AddChildContent<Counter>());
  var h1 = cut.Find("h1");
  h1.MarkupMatches(header);
  var p = cut.Find("p");
  p.MarkupMatches("<p>Current count: 0</p>");
}
Listing 8-27

Calling AddChildContent Multiple Times

What about templated components? Start by adding (or copy from the provided code download with this book) the templated component from Listings 8-28 and 8-29 (markup and code). This templated component uses two RenderFragments and one RenderFragment<TItem>. It also has a parameter to pass a Loader which is a function that grabs the items for this component. First, we will look at the RenderFragment and then the RenderFragment<TItem>.
@typeparam TItem
@if (items is null)
{
    @LoadingContent
}
else if (items.Count() == 0)
{
    @EmptyContent
}
else
{
    <div class="list-group @ListGroupClass">
        @foreach (var item in items)
        {
            <div class="list-group-item">
                @ItemContent(item)
            </div>
        }
    </div>
}
Listing 8-28

The TemplatedList Component’s Markup

using Microsoft.AspNetCore.Components;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
namespace Testing.Client.Pages
{
  public partial class TemplatedList<TItem>
  {
    IEnumerable<TItem>? items;
    [Parameter]
    public Func<ValueTask<IEnumerable<TItem>>>? Loader { get; set; }
    [Parameter]
    public RenderFragment LoadingContent { get; set; } = default!;
    [Parameter]
    public RenderFragment? EmptyContent { get; set; } = default!;
    [Parameter]
    public RenderFragment<TItem> ItemContent { get; set; } = default!;
    [Parameter]
    public string ListGroupClass { get; set; } = string.Empty;
    protected override async Task OnParametersSetAsync()
    {
      if (Loader is not null)
      {
        items = await Loader();
      }
    }
  }
}
Listing 8-29

The TemplatedList Component’s Code

Now add the TemplatedListShould class to your test project from Listing 8-30. Here, we add two parameters, one for the Loader parameter and one for the LoadingContent template. As you can see, we can use the same Add method, just like normal parameters.
using Bunit;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Testing.Client.Pages;
using Xunit;
namespace Testing.ComponentTests
{
  public class TemplatedListShould : TestContext
  {
    [Fact]
    public void RenderLoadingTemplateWhenItemsIsNull()
    {
      const string loading =
        "<div class=\"loader\">Loading...</div>";
      Func<ValueTask<IEnumerable<string>?>> loader =
        () => new ValueTask<IEnumerable<string>?>(result:null);
      var cut = RenderComponent<TemplatedList<string>>(
        parameters =>
          parameters.Add(tl => tl.Loader, loader)
                   .Add(tl => tl.LoadingContent, loading)
      );
      cut.Find("div.loader")
         .MarkupMatches(loading);
    }
  }
}
Listing 8-30

Using a RenderFragment in a Test

But what about the ItemContent parameter which uses the more complex RenderFragment<TItem>? Add a new unit test as in Listing 8-31. Here, we will pass five strings using the loader Func<ValueTask<IEnumerable<string>>>. Do note the use of the Enumerable.Repeat method to create a collection of elements. We pass the loader as a parameter to the TemplatedList<string> component, and we also pass the ItemContent, which is a RenderFragment<string>. Since this takes an argument, we use a Func<string, string> delegate which will return a RenderFragment<string> (because the Add method takes care of this).

Now we want to check if it has used the ItemContent for each item from our collection (of five “A” strings). There is a FindAll method taking a CSS selector that will return all elements that match the selector. The ItemContent RenderFragment uses a p, so we use this as the CSS selector. First, we check if the number of paragraph matches the number of items, and then we iterate over each of these and check if the markup matches the expected output.
[Fact]
public void RenderItemsCorrectly()
{
  const int count = 5;
  Func<ValueTask<IEnumerable<string>>> loader =
    () => new ValueTask<IEnumerable<string>>(
      Enumerable.Repeat("A", count));
  var cut = RenderComponent<TemplatedList<string>>(
    parameters =>
      parameters.Add(tl => tl.Loader, loader)
                .Add(tl => tl.ItemContent,
                      (context) => $"<p>{context}</p>"));
  var ps = cut.FindAll("p");
  ps.Should().NotBeEmpty();
  foreach (var p in ps)
  {
    p.MarkupMatches("<p>A</p>");
  }
}
Listing 8-31

Passing a RenderFragment<T>

Run this test; it should normally pass. And if it does not, we will discuss this in the section “Handling Asynchronous Re-renders,” so keep reading.

One final example. Let us use another component as the ItemContent and pass the context as a parameter. Add a new component called ListItem from Listing 8-32 (which is a copy-paste of the ItemContent from Listing 8-31).
<p>@Item</p>
@code {
  [Parameter]
  public string Item { get; set; } = default!;
}
Listing 8-32

The ListItem Component

Now copy and paste the RenderItemsCorrectly method , renaming it as in Listing 8-33. The only other part of this listing that needs some modification is where we pass the ItemContent parameter. If you want to use a component to pass as a RenderFragment<TItem>, you need to use the Add<ComponentType, TItem> overload, where the first generic argument is the type of the component to use and the second is the type of the generic argument for RenderFragment<TItem> . So in this specific case, the ComponentType is ListItem, and the TItem is string (because we pass an IEnumerable<string> to the TemplatedList).
[Fact]
public void RenderItemsWithListItemCorrectly()
{
  const int count = 5;
  Func<ValueTask<IEnumerable<string>?>> loader =
    () => new ValueTask<IEnumerable<string>?>(
      Enumerable.Repeat("A", count));
  var cut = RenderComponent<TemplatedList<string>>(
    parameters =>
      parameters.Add(tl => tl.Loader, loader)
                /*component*//*TItem*/
                .Add<ListItem, string>(tl => tl.ItemContent,
                   context => itemParams
                           => itemParams.Add(p => p.Item,
                                             context)
                        ));
  var ps = cut.FindAll("p");
  ps.Should().NotBeEmpty();
  foreach (var p in ps)
  {
    p.MarkupMatches("<p>A</p>");
  }
}
Listing 8-33

Passing a Component As a RenderFragment<TItem>

This Add<ListItem, string> overload takes two expressions: the first returns the parameter to set (ItemContent), and the second expression needs some deeper explanation. Let us have a look at this somewhat hard to read piece of code:
Add<ListItem, string>(
  tl => tl.ItemContent,
  context => itemParams
          => itemParams.Add(p => p.Item, context)
));

So the first argument is tl => tl.ItemContent which returns the parameter to set. The second argument is a lambda function, which takes the value for TItem (so in our case, a string), and returns another lambda function which takes a ComponentParameterCollectionBuilder<TComponent>. Does this sound familiar? Yes. It is the same type we have used to pass parameters to a component from the beginning of this section (Listing 8-18 example). Here, we add parameters to the ListItem component by calling Add.

Run this test (and the others if you like). All tests should pass. Phew!

Using Cascading Parameters

Some components use one or more cascading parameters, so to test these components, we will need to pass a value for the cascading parameter. Start by making a copy of the Counter component, and rename it to CounterWithCV. Add an Increment cascading parameter as in Listing 8-34.
@page "/counter"
<h1>Counter</h1>
<p>Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">
    Click me
</button>
@code {
    [CascadingParameter]
    public int Increment { get; set; }
    private int currentCount = 0;
    private void IncrementCount()
    {
        currentCount += Increment;
    }
}
Listing 8-34

The CounterWithVC Component

Add a new test class called CounterWithCVShould and implement the test as in Listing 8-35. As you can see, since cascading properties are identified through their type, you only need to pass the value.
using Bunit;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Testing.Client.Pages;
using Xunit;
namespace Testing.ComponentTests
{
  public class CounterWithVCShould : TestContext
  {
    [Fact]
    public void ShouldUseCascadingIncrement()
    {
      var cut = RenderComponent<CounterWithCV>(parameters =>
        parameters.AddCascadingValue(3));
      cut.Find(cssSelector: "button")
         .Click();
      cut.Find(cssSelector: "p")
         .MarkupMatches(@"<p>Current count: 3</p>");
    }
  }
}
Listing 8-35

Testing a Component with a Cascading Parameter

You can also have named cascading values, so try this: First name the Increment cascading parameter, as in Listing 8-36, and update the test as in Listing 8-37.
[CascadingParameter(Name = "Increment")]
public int Increment { get; set; }
Listing 8-36

Using a Named Cascading Parameter

var cut = RenderComponent<CounterWithCV>(parameters =>
  parameters.AddCascadingValue("Increment", 3));
Listing 8-37

Passing a Named Cascading Parameter

Using MOQ to Create Fake Implementations

We have seen that components should do one thing very well (the single responsibility principle) and that we should use services to implement logic, such as retrieving data using REST, or to implement business logic. We pass these services to the component using dependency injection. Here, we will look at how to pass dependencies to components using bUnit and how to replace your services with fake implementations to better drive your unit tests.

Injecting Dependencies with bUnit

Let us start by reviewing the FetchData component from Listing 8-38. This component takes one dependency, an IWeatherService.
@page "/fetchdata"
@using Testing.Shared
@inject IWeatherService WeatherService
<h1>Weather forecast</h1>
<p>This component demonstrates fetching data from the server.</p>
@if (forecasts == null)
{
  <p><em>Loading...</em></p>
}
else
{
  <table class="table">
    <thead>
      <tr>
        <th>Date</th>
        <th>Temp. (C)</th>
        <th>Temp. (F)</th>
        <th>Summary</th>
      </tr>
    </thead>
    <tbody>
      @foreach (var forecast in forecasts)
      {
        <tr>
          <td>@forecast.Date.ToShortDateString()</td>
          <td>@forecast.TemperatureC</td>
          <td>@forecast.TemperatureF</td>
          <td>@forecast.Summary</td>
        </tr>
      }
    </tbody>
  </table>
}
@code {
  private IEnumerable<WeatherForecast>? forecasts;
  protected override async Task OnInitializedAsync()
  {
    forecasts = await WeatherService.GetForecasts();
  }
}
Listing 8-38

The FetchData Component

When you use this component in a Blazor application, the Blazor runtime will take care of injecting this dependency. When you use the component in a bUnit test, the bUnit runtime will take care of injecting the dependency. The only thing we need to tell is which class to use to instantiate the instance.

Add a new test class to the test project, call it FetchDataShould , and complete it as in Listing 8-39. To configure dependency injection in a bUnit test, you add your dependencies to the Services property, using the same methods as regular dependency injection, AddSingleton, AddTransient, and AddScoped.
using Bunit;
using FluentAssertions;
using Microsoft.Extensions.DependencyInjection;
using Testing.Client.Pages;
using Testing.Shared;
using Xunit;
namespace Testing.ComponentTests
{
  public class FetchDataShould : TestContext
  {
    [Fact]
    public void UseWeatherService()
    {
      // Use Services for dependency injection
      Services.AddSingleton<IWeatherService,
        Testing.Shared.WeatherService>();
      var cut = RenderComponent<FetchData>();
      var rows = cut.FindAll("tbody tr");
      rows.Count.Should().Be(5);
    }
  }
}
Listing 8-39

Testing the FetchData Component

Try running this test. It fails? Look at the output of the failed test. As it turns out, the WeatherService from the shared project has a dependency of its own, an ILogger. Should we add another dependency? In this case, we should build a class implementing the ILogger interface or find an existing one. We won’t. Let us talk about fake objects.
Message:
    System.InvalidOperationException : Unable to resolve service for type 'Microsoft.Extensions.Logging.ILogger`1[Testing.Shared.WeatherService]' while attempting to activate 'Testing.Shared.WeatherService'.

Replacing Dependencies with Fake Objects

When you are testing a component, you want full control over dependencies. This means in many cases that you cannot use the real dependency. First of all, remember that tests should be fast and automatic? If the real dependency uses a database or a REST call to fetch data, this will make your test slow. Networks, disks, and databases are several factors slower than accessing data from memory. So we want to avoid these things. Also, databases and disks have memory, so when a test makes modifications to the data, the next time the test runs, it is using different data and will probably fail. So we don’t want to use the real dependency (we are testing the component, not the dependency!). So we will use a fake implementation of the dependency, and that is why it is so important to have your dependencies implement an interface. Building another class with the same interface is easy and practical.

And there are different kinds of fake objects. Let us discuss stubs and mocks as shown in Figure 8-15. As you can see, both stubs and mocks are special cases of fake objects. The terminology (stub, mock, fake) used here unfortunately is not consistent in the testing community. Some people classify fake object using different names, and some people even use taxonomies containing seven different kinds of stubs!
../images/469993_3_En_8_Chapter/469993_3_En_8_Fig15_HTML.jpg
Figure 8-15

Fake, Stub, and Mock Objects for Testing

Using Stubs

Let us start with stubs. A stub is a fake implementation of a dependency that is just there to assist in a test. Our FetchData component will fetch a couple of forecasts from the IWeatherService dependency. But how many forecasts will this return? If we use the real service, this might depend on a bunch of things which are out of our control. So we use a stub implementation of the IWeatherService where we have full control. The stub is just there to assist in the test, and we will perform our Assert phase on the subject under test, not the stub. Let me use another example. Imagine you work for a car company, and you want to test your new type of car for safety. You want to run this car into a wall and see if it will explode (like in the movies). Will you run the car into a real wall? Someone’s house? No. You will have someone build a fake wall in a controlled environment so no one will risk getting hurt. You drive the car into the wall, and then the wall has served its purpose. You will examine the car to see the outcome of the test; the wall is no longer important. This is illustrated in Figure 8-16.
../images/469993_3_En_8_Chapter/469993_3_En_8_Fig16_HTML.jpg
Figure 8-16

Using a Stub During a Test

Tests that use stubs are also known as state verification tests .

Let us build a stub for the IWeatherService . Start by adding a new class called WeatherServiceStub to the test project.

Implement the interface like Listing 8-40. Our stub has a property that will hold the data that will be returned from the service.
private class WeatherServiceStub : IWeatherService
{
  public IEnumerable<WeatherForecast> FakeForecasts { get; set; }
    = default!;
  public ValueTask<IEnumerable<WeatherForecast>> GetForecasts()
    => new ValueTask<IEnumerable<WeatherForecast>>(
         FakeForecasts);
}
Listing 8-40

Implementing an IWeatherService Stub

Now update the UseWeatherService test as in Listing 8-41. We create an instance of the stub, initialize it with the data we want, and then pass it to dependency injection as a singleton. When the FetchData component gets initialized, we will use the stub, and we are sure that our service returns five rows of data (or a different number; that is why I use a const for easy update).
[Fact]
public void UseWeatherService()
{
  const int nrOfForecasts = 5;
  var stub = new WeatherServiceStub
  {
    FakeForecasts = Enumerable.Repeat(new WeatherForecast(),
                                      nrOfForecasts)
  };
  Services.AddSingleton<IWeatherService>(stub);
  var cut = RenderComponent<FetchData>();
  var rows = cut.FindAll("tbody tr");
  rows.Count.Should().Be(nrOfForecasts);
}
Listing 8-41

Testing the FetchData Component with a Stub

Run the test. It should pass.

Using Mocks

So what is a mock? A mock is a fake implementation where we want to verify if the subject under test called certain methods and properties on the mock. A mock therefore works a little like a data recorder, remembering which methods were called, even recording the values of the arguments in the method call. It should not come as a surprise that building a mock is a lot more work! When you use a mock in a test, you will do your Assert phase through the mock, with questions like “Did the subject under test call this method?” Let us use the car example again. Now we want to see if the driver of the car gets hurt in a frontal crash into a wall. We already have a wall, but now we need a driver. Any volunteers? No? Of course, not. We will mimic the driver (a mock object) using a crash test dummy. These dummies look a lot like a human (if you are Homer Simpson) and are crammed full of sensors. You let the car crash into the wall. After the crash, you are not interested in the wall, neither the car. You will ask the dummy (the mock remember) where it hurts. Again, this is illustrated in Figure 8-17.
../images/469993_3_En_8_Chapter/469993_3_En_8_Fig17_HTML.jpg
Figure 8-17

Using a Mock During Testing

Tests like these are known as object interaction tests .

Let us update the FetchData component to perform some logging, so add an @inject for an ILogger, and use it in the OnInitializedAsync as in Listing 8-42.
@page "/fetchdata"
@using Microsoft.Extensions.Logging
@using Testing.Shared
@inject IWeatherService WeatherService
@inject ILogger logger
...
    protected override async Task OnInitializedAsync()
    {
        logger.LogInformation("Fetching forecasts");
        forecasts = await WeatherService.GetForecasts();
    }
}
Listing 8-42

Update the FetchData Component to Use Logging

So we want to test if the ILogger is used during the OnInitializedAsync. We need a mock implementation because we don’t want to have to parse log files. Add a new class to your test project called LoggerMock as in Listing 8-43. Implementing this class alone takes some work! We will next look at how we can make this easier. Our mock logger simply records a couple of arguments in the Journal list.
private class LoggerMock : ILogger
{
  public List<(LogLevel logLevel, object? state)> Journal
    { get; set; } = new List<(LogLevel,object?)>();
  public IDisposable BeginScope<TState>(TState state)
    => throw new NotImplementedException();
  public bool IsEnabled(LogLevel logLevel)
    => true;
  public void Log<TState>(LogLevel logLevel, EventId eventId,
                          TState state, Exception? exception,
                      Func<TState, Exception?, string> formatter)
  {
    Journal.Add((logLevel, state));
  }
}
Listing 8-43

Implementing an ILogger Mock

Add a new unit test to the FetchDataShould class like in Listing 8-44.
[Fact]
public void UseProperLogging()
{
  const int nrOfForecasts = 5;
  var stub = new WeatherServiceStub
  {
    FakeForecasts = Enumerable.Repeat(new WeatherForecast(),
                                      nrOfForecasts)
  };
  Services.AddSingleton<IWeatherService>(stub);
  LoggerMock logger = new LoggerMock();
  Services.AddSingleton<ILogger>(logger);
  var cut = RenderComponent<FetchData>();
  logger.Journal.Count.Should().Be(1);
  logger.Journal.First().state.Should().NotBeNull();
  logger.Journal.First().state!.ToString().Should().Contain("Fetching forecasts");
}
Listing 8-44

Testing the FetchData Component Using a Mock

So we create a stub for the IWeatherService , a mock for the ILogger, and then we render the component. Now we want to check the Journal of the LoggerMock. There should be one call to the logger, so we check the length of the Journal. Then we check the entry’s state to see if it contains the message. All straight forward but a lot of work!

Run all your tests. The UseWeatherService test breaks! Why? Because we introduced another dependency, so we need to dependency inject a logger in this test too. I will leave the fixing in your capable hands.

Implementing Stubs and Mocks with MOQ

How can we implement stubs and mocks with a lot less work? Other people have been asking the same question, and some of them built libraries that make this possible. Generally, these libraries are known as isolation frameworks . Isolation frameworks allow you to quickly generate stubs and mocks for classes and interfaces, where you implement just the methods you need for the test, and verify if the subject under test invoked methods with certain arguments a certain number of times. Here, we will look at MOQ which is currently one of the most popular in the testing community. We will cover a lot of features of MOQ here, but if you want to learn more, you can visit https://documentation.help/Moq.

Start by adding the MOQ NuGet package to the test project. Now copy the UseWeatherServices method and rename it to UseWeatherServicesMOQ. Change its implementation like Listing 8-45. First, we create the forecasts data we want the IWeatherService to return. Next, we create an instance of Mock<IWeatherService> which is a class from MOQ. This class allows us to Setup methods from the interface and Returns a certain result. It is that simple to provide a stub implementation. But MOQ allows you to go further and makes the method return different results, depending on the arguments, for example.

Next, we configure bUnit’s dependency injection to inject a singleton instance, passing the stub.Object, which is an instance implementing the IWeatherService interface. No need to build our own class to create a stub.

Our FetchData component also needs a logger, but here we are not interested in the interaction between the component and the logger, so we create another stub. The rest of the test remains unchanged.
[Fact]
public void UseWeatherServiceMOQ()
{
  const int nrOfForecasts = 5;
  var forecasts = Enumerable.Repeat(new WeatherForecast(), nrOfForecasts);
  Mock<IWeatherService> stub = new Mock<IWeatherService>();
  stub.Setup(s => s.GetForecasts())
      .Returns(new ValueTask<IEnumerable<WeatherForecast>>(forecasts));
  Services.AddSingleton<IWeatherService>(stub.Object);
  Mock<ILogger> loggerStub = new Mock<ILogger>();
  Services.AddSingleton<ILogger>(loggerStub.Object);
  var cut = RenderComponent<FetchData>();
  var rows = cut.FindAll("tbody tr");
  rows.Count.Should().Be(nrOfForecasts);
}
Listing 8-45

Implementing a Stub with MOQ

Run the test; it should pass.

Now it is time to implement a mock, where we want to see if the FetchData component will invoke the logger. Copy the UseProperLogging method and name it UseProperLoggingMOQ as in Listing 8-46. Here, you should focus on the Verify method. Here, we verify if the Log method got called, and we can state how many times. You can choose between Never, Once, AtLeast, AtMost, Exactly, and more. The Log method takes a bunch of arguments, and the way this Log method works is somewhat awkward. The first argument is of type LogLevel, which we check if the LogLevel.Information value was used with
It.Is<LogLevel>(l => l == LogLevel.Information)
Each argument is represented with a check of the arguments’ value. You can also ignore the value of the argument with It.IsAny<T>, specifying the type of the argument. This type of argument is needed to disambiguate overloading. Other arguments work in a similar way, even generic arguments. For example, if an argument is of type List<T> and you cannot know T, you use It.Is<List<It.IsAnyType>>. We need to use that here because of specific implementation details of ILogger.
[Fact]
public void UseProperLoggingMOQ()
{
  const int nrOfForecasts = 5;
  var forecasts = Enumerable.Repeat(new WeatherForecast(), nrOfForecasts);
  Mock<IWeatherService> stub = new Mock<IWeatherService>();
  stub.Setup(s => s.GetForecasts())
      .Returns(new ValueTask<IEnumerable<WeatherForecast>>(forecasts));
  Services.AddSingleton<IWeatherService>(stub.Object);
  Mock<ILogger> loggerMock = new Mock<ILogger>();
  Services.AddSingleton<ILogger>(loggerMock.Object);
  var cut = RenderComponent<FetchData>();
  loggerMock.Verify(
    l => l.Log(
      It.Is<LogLevel>(l => l == LogLevel.Information),
      It.IsAny<EventId>(),
      It.Is<It.IsAnyType>(
        (msg,t) => msg.ToString()!
                      .Contains("Fetching forecasts")),
      It.IsAny<Exception>(),
      It.Is<Func<It.IsAnyType, Exception?, string>>(
        (v,t)=>true))
    , Times.Once);
}
Listing 8-46

Implementing a Mock Using MOQ

Run the test. It should pass.

Writing bUnit Tests in Razor

When you build unit tests with bUnit, you sometimes end up with long tests because of all the markup that gets generated. Also, the MarkupMatches method takes a string, and if your markup uses HTML attributes, you need to escape your quotes with \. For these kinds of tests, we can also use razor to author tests. Writing unit tests with razor requires two things: the project needs to reference the razor SDK, meaning your test project should set the SDK type to razor:
<Project Sdk="Microsoft.NET.Sdk.Razor">
Second, you should add an _Imports.razor file to the test project for easy reference, as in Listing 8-47.
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.JSInterop
@using Microsoft.Extensions.DependencyInjection
@using AngleSharp.Dom
@using Bunit
@using Bunit.TestDoubles
@using Xunit
Listing 8-47

The _Imports.razor File for Test Projects

I do advise to add your project’s namespaces here too.

The First Razor Test

In your test project, add a new razor component called RCounterShould as in Listing 8-48. Here, I will prefix the razor unit tests with an R, so we don’t get a name conflict with our other CounterShould test class. We will make the test inherit from TestContext, just like our test classes written in C#. Then we add a @code section and put our xUnit test method in there. Because this is a razor file, we can write the test’s markup using razor inside the Render method.

And inside the MarkupMatches method , we can also write the markup using plain razor. This makes writing tests like these simpler and agreeable.
@inherits Bunit.TestContext
@code {
  [Fact]
  public void RenderCorrectlyWithInitialZero()
  {
    var cut = Render(@<Counter />);
    cut.Find("p")
       .MarkupMatches(@<p>Current count: 0</p>);
  }
  [Fact]
  public void IncrementCounterWhenButtonIsClicked()
  {
    var cut = RenderComponent<Counter>();
    cut.Find(cssSelector: "button")
       .Click();
    cut.Find(cssSelector: "p")
       .MarkupMatches(@"<p>Current count: 1</p>");
  }
}
Listing 8-48

Writing a Simple Unit Test with Razor

What about passing parameters? Add a new component called RTwoWayCounterShould like in Listing 8-49. Since we can render our component using plain razor, we can pass parameters inside the razor syntax as shown in the first test method! The second test method illustrates how we can test two-way data binding, again using the same familiar razor syntax.
@inherits Bunit.TestContext
@code {
  [Fact]
  public void IncrementCounterWhenButtonIsClicked()
  {
    var cut = Render(@<TwoWayCounter CurrentCount="1" Increment="2"/>);
    cut.Find("button").Click();
    cut.Find("p")
       .MarkupMatches(@<p>Current count: 3</p>
  );
  }
  [Fact]
  public void TriggerChangedEventForCurrentCounter2()
  {
    int currentCount = 1;
    var cut = Render(@<TwoWayCounter
                        @bind-CurrentCount="currentCount"
                        Increment="2"/>
    );
    cut.Find(cssSelector: "button")
       .Click();
    currentCount.Should().Be(3);
  }
}
Listing 8-49

Passing Parameters in a Razor Test

Let us look at an example that uses ChildContent . Add a new razor component called RAlertShould to the test project as in Listing 8-50. The Alert component uses ChildContent, and we can pass this by nesting the child content inside the Alert markup. And to see if the component gets rendered as expected, we can use simple HTML markup inside the MarkupMatches method.
@inherits Bunit.TestContext
@code {
  [Fact]
  public void RenderSimpleChildContent()
  {
    var cut = Render(
      @<Alert>
        <h1>Hello world!</h1>
      </Alert>
    );
    cut.MarkupMatches(
      @<div class="alert alert-secondary mt-4" role="alert">
        <h1>Hello world!</h1>
      </div>
    );
  }
}
Listing 8-50

Testing a Component with ChildContent

Add another razor component, called RTemplatedListShould from Listing 8-51. Again, we want to see if the component displays the loading RenderFragment when the items are null. Passing a RenderFragment is again done using razor.
@inherits Bunit.TestContext
@code {
  [Fact]
  public void RenderLoadingTemplateWhenItemsIsNull()
  {
    RenderFragment loading =
      @<div class="loader">Loading...</div>;
    Func<ValueTask<IEnumerable<string>?>> loader =
      () => new ValueTask<IEnumerable<string>?>(
              result: null);
    var cut = Render(
      @<TemplatedList Loader="@loader">
        <LoadingContent>
          <div class="loader">Loading...</div>
        </LoadingContent>
       </TemplatedList>
    );
    cut.Find("div.loader")
       .MarkupMatches(loading);
  }
}
Listing 8-51

Using a Razor Test for a Templated Component

Handling Asynchronous Re-renders

When you build a component that overrides OnInitializedAsync or OnParametersSetAsync, your component will at least render itself twice – first, when the component gets created and after completion of the OnInitializedAsync and again after completion of each OnParametersSetAsync.

Inside a bUnit test, this can give you issues. Let us look at an example.

Add the following unit test from Listing 8-52 to the RTemplatedListShould class. In this test, we make the loader really asynchronous using the TaskCompletionSource<T> class. Instances of this class have a Task<T> which will continue execution by calling the SetResult method. Until then the Task will block any awaiter. This allows us to render the component, see the loading UI, then make the Task complete by calling SetResult, and then see if the items get rendered.
[Fact]
public void RenderItemsAftersItemsLoadedAsyncCorrectly()
{
  const int count = 5;
  var tcs = new TaskCompletionSource<IEnumerable<string>?>();
  Func<ValueTask<IEnumerable<string>?>> loader =
    () => new ValueTask<IEnumerable<string>?>(tcs.Task);
  var cut = Render(
    @<TemplatedList Loader="@loader">
      <LoadingContent>
        <div class="loader">Loading...</div>
      </LoadingContent>
      <ItemContent Context="item">
        <ListItem Item="@item" />
      </ItemContent>
    </TemplatedList>
  );
  cut.Find("div.loader")
     .MarkupMatches(@<div class="loader">Loading...</div>);
  // Complete the loader task,
  // this should rerender the component asynchronously
  tcs.SetResult(Enumerable.Repeat("A", count));
  var ps = cut.FindAll("p");
  ps.Should().NotBeEmpty();
  foreach (var p in ps)
  {
    p.MarkupMatches(@<p>A</p>);
  }
}
Listing 8-52

Testing Asynchronous Re-renders

Run the test. It will fail! Why? Because our component will render the UI on another thread, and the test will check the UI before rendering completes. So we need to wait a bit till the UI rendering completes. How can we do this? Add this line of code after the SetResult call, with the complete method in Listing 8-53.
cut.WaitForState(() => cut.FindAll("p").Any());
The WaitForState method will wait till the condition returns true. We know that the UI will render a bunch of paragraphs, so we wait till we see them. The WaitForState also has a parameter (not shown here) to set the timeout, which has a default value of 1 second. If the cut does not pass the condition within the timeout, the test will fail with the WaitForFailedException.
[Fact]
public void RenderItemsAftersItemsLoadedAsyncCorrectly()
{
  const int count = 5;
  var tcs = new TaskCompletionSource<IEnumerable<string>?>();
  Func<ValueTask<IEnumerable<string>?>> loader =
    () => new ValueTask<IEnumerable<string>?>(tcs.Task);
  var cut = Render(
    @<TemplatedList Loader="@loader">
      <LoadingContent>
        <div class="loader">Loading...</div>
      </LoadingContent>
      <ItemContent Context="item">
        <ListItem Item="@item" />
      </ItemContent>
    </TemplatedList>
  );
  cut.Find("div.loader")
     .MarkupMatches(@<div class="loader">Loading...</div>);
  // Complete the loader task,
  // this should rerender the component asynchronously
  tcs.SetResult(Enumerable.Repeat("A", count));
  // Wait for rendering to complete
  cut.WaitForState(() => cut.FindAll("p").Any());
  var ps = cut.FindAll("p");
  ps.Should().NotBeEmpty();
  foreach (var p in ps)
  {
    p.MarkupMatches(@<p>A</p>);
  }
}
Listing 8-53

Testing Asynchronous Re-renders

Configuring Semantic Comparison

The bUnit testing library uses the AngleSharp Diffing library to compare the generated markup with the expected markup in the MarkupMatches method. You can find AngleSharp on GitHub at https://github.com/AngleSharp/AngleSharp.Diffing. To make your tests more robust, you can configure how the semantic comparison works; for example, we can tell it to ignore certain HTML attributes and elements.

Why Do We Need Semantic Comparison?

Using strings to compare markup is too sensitive to small changes in the markup. For example, formatting your code might add some whitespace, and since string comparison will compare each character, a working test will suddenly fail. And there are many more innocent changes that will break a test, for example, changing the order of attributes, or reordering the classes in the class attribute, or adding comments. Semantic comparison will ignore all of these changes, resulting in tests that will not break because of a simple change.

Customizing Semantic Comparison

Remember one of our previous tests, where we told the MarkupMatches method to ignore the attribute (Listing 8-16). The AngleSharp Diffing library allows us to use special attributes to ignore certain elements and attributes; for example, <div style:ignore> will ignore the style attribute’s contents. We can also make it ignore certain HTML elements; for example, add the test from Listing 8-54 to the AlertShould class.
[Fact]
public void RenderCorrectly()
{
  var cut = RenderComponent<Alert>(parameters =>
  parameters.AddChildContent("<p>Hello world!</p>"));
  cut.MarkupMatches(@"
    <div class=""alert alert-secondary mt-4"" role=""alert"">
        <p diff:ignore></p>
    </div>
  ");
}
Listing 8-54

Ignoring an Element with Semantic Comparison

We can do the same with razor tests, for example, Listing 8-55, which should be added to the RAlertShould razor file.
[Fact]
public void RenderCorrectly()
{
  var cut = Render(
    @<Alert>
      <h1>Hello world!</h1>
    </Alert>
    );
  cut.MarkupMatches(
    @<div class="alert alert-secondary mt-4" role="alert">
      <h1 diff:ignore></h1>
    </div>
    );
  }
Listing 8-55

Ignoring an Element with a Razor Test

By default, semantic comparison will ignore whitespace, but in some cases, you want to verify if the component actually renders some whitespace. Do this with diff:whitespace="preserve".

You can also tell semantic comparison to ignore case or use a regular expression for your comparison.

Note

Regular expressions allow you to test for complex patterns in strings with a concise syntax. Regular expressions were invented in 1951, and we are still using them. What else was invented more than half a century ago that we are still using in IT? Learning regular expressions is something worthwhile investing in for your future in IT!

Let us test the simple Card component from Listing 8-56.
<h3 id="card-@Id">Card @Id</h3>
@code {
    [Parameter]
    public int Id { get; set; }
}
Listing 8-56

A Simple Card Component

A unit test that will check if the id attribute matches card- followed by one to four digits and the content matches Card with one to four digits looks like Listing 8-57. We also want the test to ignore the casing on the card’s contents.
using Bunit;
using Testing.Client.Pages;
using Xunit;
namespace Testing.ComponentTests
{
  public class CardShould : TestContext
  {
    [Fact]
    public void RenderCorrectlyWithProperId()
    {
      var cut = RenderComponent<Card>();
      cut.MarkupMatches(@"<h3 diff:ignorecase diff:regex id:regex=""card-\d{1,4}"">card \d{1,4}</h3>");
    }
  }
}
Listing 8-57

Ignore Casing and Using Regular Expressions

Summary

In this chapter, we had a look at unit testing. With unit testing, you can see if your code and components behave as expected, and also it allows you to test if they continue behaving, so small changes that cause bugs are found as fast as possible. Good unit tests are fast, consistent, repeatable, and automatic. We have seen that testing Blazor components becomes very practical with bUnit, and we can author tests using C# or Razor. And with MOQ, we can quickly generate stubs and mocks to replace dependencies in our tests.