English
Français

Blog of Denis VOITURON

for a better .NET world

How to create Unit Tests for the FluentUI Blazor project?

Posted on 2023-08-30

Overview

In the dynamic field of Blazor web development, creating applications that are both innovative and reliable is a top priority. As the complexity of our projects increases, so does the risk of bugs and malfunctions. This is where the importance of unit testing comes into its own.

Unit testing offers a systematic approach to the verification of small individual units of code. By subjecting these units to a variety of scenarios and inputs, developers can ensure that their code behaves as expected, identify weaknesses and detect problems early in the development cycle. In the context of Blazor, where backend and frontend logic converge, unit testing plays a central role in maintaining stability and performance.

In this article, we explore the world of unit testing used in the FluentUI Blazor projects. We’ll dive into the basic concepts, understand its importance in maintaining code quality, and discover best practices for creating tests quickly and efficiently.

Demo

Unit Tests

Automated testing is a great way to ensure that the application code does what its developers want it to do.

There are different types of tests that can be used to validate the code. 3 of them are:

  1. Unit tests

    A unit test is a test that exercises individual software components or methods, also known as “unit of work”. Unit tests should only test code within the developer’s control. They do not test infrastructure concerns. Infrastructure concerns include interacting with databases, file systems, and network resources.

  2. Integration tests

    An integration test differs from a unit test in that it exercises two or more software components’ ability to function together, also known as their “integration.” These tests operate on a broader spectrum of the system under test, whereas unit tests focus on individual components. Often, integration tests do include infrastructure concerns.

  3. Load tests

    A load test aims to determine whether or not a system can handle a specified load, for example, the number of concurrent users using an application and the app’s ability to handle interactions responsively.




Why unit test?


Copied from Unit testing Best Practice.




Six Best practices

See detailed best practices.

  1. Naming your tests

    The name of your test should consist of three parts:

    • The name of the method being tested.
    • The scenario under which it’s being tested.
    • The expected behavior when the scenario is invoked.

    Why?

    • Naming standards are important because they explicitly express the intent of the test.

    Example:

    [Fact]
    public void Add_SingleNumber_ReturnsSameNumber()
    


  2. Arranging your tests

    Arrange, Act, Assert is a common pattern when unit testing. As the name implies, it consists of three main actions:

    • Arrange your objects, creating and setting them up as necessary.
    • Act on an object.
    • Assert that something is as expected.

    Why?

    • Clearly separates what is being tested from the arrange and assert steps.
    • Less chance to intermix assertions with “Act” code.

    Example:

    [Fact]
    public void Add_EmptyString_ReturnsZero()
    {
       // Arrange
       var stringCalculator = new StringCalculator();
    
       // Act
       var actual = stringCalculator.Add("");
    
       // Assert
       Assert.Equal(0, actual);
    }
    


  3. Write minimally passing tests

    The input to be used in a unit test should be the simplest possible in order to verify the behavior that you are currently testing.

    Why?

    • Tests become more resilient to future changes in the codebase.
    • Closer to testing behavior over implementation.


  4. Avoid logic in tests

    When writing your unit tests avoid manual string concatenation and logical conditions such as if, while, for, switch, etc.

    Why?

    • Less chance to introduce a bug inside of your tests.
    • Focus on the end result, rather than implementation details.

    Example:

    [Theory]
    [InlineData("0,0,0", 0)]
    [InlineData("0,1,2", 3)]
    [InlineData("1,2,3", 6)]
    public void Add_MultipleNumbers_ReturnsSumOfNumbers(string input, int expected)
    {
       var stringCalculator = new StringCalculator();
    
       var actual = stringCalculator.Add(input);
    
       Assert.Equal(expected, actual);
    }
    


  5. Prefer helper methods to setup and teardown

    If you require a similar object or state for your tests, prefer a helper method than leveraging constructor or Setup and Teardown attributes if they exist.

    Why?

    • Less confusion when reading the tests since all of the code is visible from within each test.
    • Less chance of setting up too much or too little for the given test.
    • Less chance of sharing state between tests, which creates unwanted dependencies between them.

    Example:

    // Bad
    // public StringCalculatorTests()
    // {
    //    stringCalculator = new StringCalculator();
    // }
    
    [Fact]
    public void Add_TwoNumbers_ReturnsSumOfNumbers()
    {
       var stringCalculator = CreateDefaultStringCalculator();
    
       var actual = stringCalculator.Add("0,1");
    
       Assert.Equal(1, actual);
    }
    
    private StringCalculator CreateDefaultStringCalculator()
    {
       return new StringCalculator();
    }
    


  6. Avoid multiple acts

    When writing your tests, try to only include one Act per test. Common approaches to using only one act include:

    Create a separate test for each act or use parameterized tests.

    Why?

    • When the test fails it is not clear which Act is failing.
    • Ensures the test is focussed on just a single case.
    • Gives you the entire picture as to why your tests are failing.



Code Coverage

Unit tests help to ensure functionality and provide a means of verification for refactoring efforts. Code coverage is a measurement of the amount of code that is run by unit tests - either lines, branches, or methods.

This chapter discusses the usage of code coverage for unit testing with Coverlet and report generation using ReportGenerator.

  1. Requirements

    Include the NuGet Packages coverlet.msbuild and coverlet.collector in your Unit Tests Project (csproj).

    <PackageReference Include="coverlet.msbuild" Version="3.2.0" />
    <PackageReference Include="coverlet.collector" Version="3.2.0" />
    
  2. Tools

    To generate a code coverage report locally, install these tools: Coverlet and ReportGenerator.

    dotnet tool install --global coverlet.console --version 3.2.0
    dotnet tool install --global dotnet-reportgenerator-globaltool --version 5.1.20
    

    Use this command to list and verify existing installed tools:

    dotnet tool list --global
    
  3. Start a code coverage

    You can start the unit test and code coverage tool using this command (in the solution folder). Each unit test project folders will contain a file coverage.cobertura.xml.

    dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=cobertura
    
  4. Generate a report

    Merge and convert all Cobertura.xml files to an HTML report (change the sample folder Temp/Coverage).

    reportgenerator "-reports:**/*.cobertura.xml" "-targetdir:C:\Temp\Coverage" -reporttypes:HtmlInline_AzurePipelines
    



FluentUI Blazor Unit Tests

In the FluentUI.Blazor project, all Blazor unit tests use the bUnit library. Its objective is to facilitate the writing of complete and stable unit tests.

With bUnit, you can:

bUnit builds on top of existing unit testing frameworks such as xUnit, NUnit, and MSTest

Example with a simple parameter:

   [Fact]
   public void MyButton_Basic_Width()
   {
       // Arrange
       using var ctx = new Bunit.TestContext();

       // Act
       var button = ctx.RenderComponent<MyButton>(parameters =>
       {
           parameters.Add(p => p.Width, "100px")
       });

       // Assert
       button.MarkupMatches(@"<fluent-button appearance=""neutral"" style=""width: 100px;"" />");
   }

In the FluentUI.Blazor project, we added a Verify method to generate a .received.html file which will be compared to a predefined .verified.html file.

   <!-- MyToolbar_Render_TwoButtons.verified.html -->
   <div class="stack-horizontal">
       <div class="my-toolbar">
           <fluent-button appearance="neutral">Button 1</fluent-button>
           <fluent-button appearance="neutral">Button 2</fluent-button>
       </div>
   </div>

Watch this video for a live demonstration

Languages

EnglishEnglish
FrenchFrançais

Follow me

Recent posts