How-To: Write bUnit Tests for Components with EventCallbacks

Component callbacks allow developers to build Blazor components that fire events to their parent components/pages to notify them of state changes. The parent components can then respond to those changes as well. bUnit supports EventCallbacks as parameters on those components, so that we can validate their behavior.

First, we must have a Blazor project and corresponding test project. Either open an existing solution or please follow the steps in the How-To: Add bUnit Test Project to Blazor Solution article to get both projects and all of the required packages. For this article, we called the projects: Blazor.HowTo and Blazor.HowTo.Tests.

Then, we need a Blazor component in our project. If we don’t already have one, we can create one using the steps in the How-To: Define EventCallback on Blazor Component article.

Component With EventCallback

Let’s review the markup and code from the component with EventCallback article:

<div class="col-12 pt-1 rounded border"
     style="background-color: lightgray; cursor: pointer"
     @onclick="AdClicked">
    <img class="float-left mr-2 mt-2 align-items-center" src="@ImageLink" />
    <h5><strong>@Title</strong></h5>
    <p style="font-size: small">
        @ChildContent
    </p>
</div>

@code {
    [Parameter]
    public string Title { get; set; }

    [Parameter]
    public string ImageLink { get; set; }

    [Parameter]
    public RenderFragment ChildContent { get; set; }

    [Parameter]
    public EventCallback AdClicked { get; set; }
}

As we can see, we defined an EventCallback parameter that is invoked whenever the user clicks on the BannerAd component.

Create Test Class

Now, let’s create the BannerAdTests class in the Blazor.HowTo.Tests project and Components folder.

using Blazor.HowTo.Components;
using Bunit;
using Xunit;

namespace Blazor.HowTo.Tests.Components
{
    public class BannerAdTests
    {
        [Fact]
        public void InitialRender_WithEventCallback()
        {
            // arrange
            var ctx = new TestContext();
            var eventCalled = false;

            // act
            var comp = ctx.RenderComponent<BannerAd>(parameters => parameters
                    .Add(p => p.Title, "Test Ad")
                    .Add(p => p.ImageLink, "/foo/test.png")
                    .AddChildContent("Test <strong>ad</strong> description.")
                    .Add(p => p.AdClicked, () => { eventCalled = true; })
                );

            // assert
            var expectedHtml =
@"  <div class=""col-12 pt-1 rounded border""
         style=""background-color: lightgray; cursor: pointer"">
      <img class=""float-left mr-2 mt-2 align-items-center""
           src=""/foo/test.png"">
      <h5>
        <strong>Test Ad</strong>
      </h5>
      <p style=""font-size: small"">Test <strong>ad</strong> description.</p>
    </div>
";
            comp.MarkupMatches(expectedHtml);
            Assert.False(eventCalled);
        }

        [Fact]
        public void AdClicked()
        {
            // arrange
            var ctx = new TestContext();
            var eventCalled = false;

            var comp = ctx.RenderComponent<BannerAd>(parameters => parameters
                .Add(p => p.Title, "Test Ad")
                .Add(p => p.ImageLink, "/foo/test.png")
                .AddChildContent("Test <strong>ad</strong> description.")
                .Add(p => p.AdClicked, () => { eventCalled = true; }));

            // act
            comp.Find("div").Click();

            // assert
            Assert.True(eventCalled);
        }
    }
}

In the InitialRender_WithEventCallback test method (lines #9-38), we set the component parameters on the RenderComponent method (see the How-To: Write bUnit Tests for Components with Parameters article for details on that).

  • In addition to normal parameters, we set the AdClicked EventCallback parameter as well (line #21).
  • In the event handler, we set the test’s eventCalled variable to true, so we know when the EventCallback was invoked.
  • The EventCallback does not add any HTML to the expected markup.
  • The first test validates that the EventCallback was not invoked by checking that eventCalled is still false.

In the AdClicked test method (lines #40-58), we simulate the onclick event within the BannerAd component to validate that our event handler gets called.

  • First, in the test setup, we render the component just like our first test… with eventCalled variable initialized to false, and the AdClicked EventCallback parameter set.
  • Then, we use the Find method to retrieve the container div within our BannerAd markup (line #54). Find uses the CSS naming convention to look up elements.
  • Next, we call the Click method to simulate the onclick event on this element (line #54).
  • Finally, we validate that the eventCalled variable was changed in the event handler (line #75)… verifying that the EventCallback was invoked.
  • Since this operations does not cause any change to the component’s HTML markup, we didn’t verify the markup.

These two tests verify that our EventCallback was set up correctly and that it was invoked in response to an event within the BannerAd component.

Finally, let’s launch the new tests in the Visual Studio TestExplorer and verify that they pass as we expect.

In conclusion, we can pass component EventCallbacks as parameters in the bUnit TestContext.RenderComponent method (just like any other component parameters). EventCallbacks are invoked by either our code or in response to another component event. Our tests showed how to validate that those EventCallbacks are called as expected.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s