Testing with bUnit - Overview
bUnit extends other unit testing frameworks (xUnit, Nunit, MSTest, etc) for Blazor-specific unit tests.
features
- includes a semantic HTML comparer so that not all possible cases must be manually tested.
 - supports dependency injection and passing parameters
 - testing user interactions and event handlers
 - testing IJSRuntime
 - testing authorization
 - mocking an HttpClient
 
simple unit test example
public void CanUpdatePiePrice() 
{
    // Arrange
    var pie = new Pie() { Name = "Sample pie", Price = 12.95M };
    // Act
    pie.Price = 20M;
    // Assert
    Assert.Equal(20M, pie.Price);
}
using bunit
process
- Install the bUnit Visual Studio template
dotnet new --install bunit.template - Create a bUnit project
dotnet new bunit --framework xunit -o project-name - Add the bUnit project to your Solution
 - From the bUnit project, add a project reference to the main project
 - Start writing unit tests
 
writing unit tests
bUnit supports writing tests in C# (.cs) or Razor files (.razor).
razor example
Assuming this Component:MainProject/Components/SampleComponent.razor
<h3>Inbox</h3>
@if (@MessageCount > 0)
{
    <h4>You currently have @MessageCount questions from employees!</h4>
}
else
{
    <h4>No questions from employees! All good!</h4>
}
@code
{
    [Parameter]
    public int MessageCount { get; set; }
}
Unit test for the above Component:TestProject/SampleComponentTests.razor
@code {
    [Fact]
    public void RenderSampleComponentWithValueGreaterThanZero()
    {
        // Arrange
        // TestContext provides access to the Component
        using var ctx = new TestContext();
        // Act
        // cut = component under test
        // use the context's Render method and pass in the Component to be tested, including a test value for the MessageCount parameter:
        var cut = ctx.Render(@<SampleComponent MessageCount="3" />);
        // Assert
        // pass in the expected HTML output:
        cut.MarkupMatches("<h3>Inbox</h3><h4>You currently have 3 questions from employees!</h4>"); 
    }
    [Fact]
    public void RenderSampleComponentWithZero()
    {
        // Arrange
        using var ctx = new TestContext();
        // Act
        var cut = ctx.Render(@<SampleComponent MessageCount="0" />);
        // Assert
        cut.MarkupMatches("<h3>Inbox</h3><h4>No questions from employees! All good!</h4>"); 
    }
}
Alternatively, inherit TestContext to avoid creating one in each method:
@inherits TestContext
@code
{
    [Fact]
    public void RenderSampleComponentWithValueGreaterThanZero()
    {
        var cut = ctx.Render(@<SampleComponent MessageCount="3" />);
        cut.MarkupMatches("<h3>Inbox</h3><h4>You currently have 3 questions from employees!</h4>"); 
    }
    // ...
}
test only a part of a component
TestProject/EmployeeCardTests.razor
@using BethanysPieShopHRM.Shared.Domain
@inherits TestContext
@code 
{
    private Employee _employee;
    public EmployeeCardTests()
    {
        var jobCategory = new JobCategory { JobCategoryId = 3, JobCategoryName = "Management" };
        var country = new Country { CountryId = 1, Name = "Belgium" };
        _employee = new Employee()
        {
            MaritalStatus = MaritalStatus.Single,
            BirthDate = new DateTime(1989, 3, 11),
            City = "Brussels",
            Email = "bethany@bethanyspieshop.com",
            EmployeeId = 1,
            FirstName = "Bethany",
            LastName = "Smith",
            Gender = Gender.Female,
            PhoneNumber = "324777888978978",
            Smoker = false,
            Street = "Grote Markt 1",
            Zip = "1000",
            JobCategory = jobCategory,
            JobCategoryId = jobCategory.JobCategoryId,
            Comment = "Lorem Ipsum",
            ExitDate = null,
            JoinedDate = new DateTime(2015, 3, 1),
            Country = country,
            CountryId = country.CountryId
        };
    }
    [Fact]
    public void RenderEmployeeCardDetailNavLink()
    {
        // render the Component:
        var cut = Render(@<EmployeeCard Employee="_employee" />);
        // find an element by ID and test that its markup matches
        // note that the attributes listed (href, id, class) do not have to be in the same order as in the code:
        cut.Find("#detailnavlink").MarkupMatches(
            @"<a href=""employeeedit/1"" id=""detailnavlink"" class=""btn btn-outline-primary btn-sm mb-1"">Edit employee</a>");
    }
}
writing advanced unit tests
testing an injected service
TestProject/InboxWidgetTests.razor
@using BethanysPieShopHRM.App.Components.Widgets
@using BethanysPieShopHRM.App
@inherits TestContext
@code
{
    public InboxWidgetTests() 
    {
        // bUnit exposes the services collection on the TestContext, so services can be injected into the test:
        this.Services.AddScoped<ApplicationState>();
    }
    [Fact]
    public void RenderInboxWidgetWIthValueGreaterThanZero()
    {
        // access the ApplicationState service:
        var applicationState = this.Services.GetService<ApplicationState>();
        applicationState.NumberOfMessages = 5;
        var cut = Render(@<InboxWidget />);
        cut.MarkupMatches("<h3>Inbox</h3><h4>You currently have 5 questions from employees!</h4>");
    }
}