Unit test a Blazor component.
By: Xavier Solau
Tags: Software Development, Unit Testing, Web Development, Dotnet Core, Blazor

How about testing some behavior now?

So far we have seen how we can set up our test component and how to test the resulting HTML elements. Now we are going a bit further, we are going to make some interaction with the component to see if it behaves as expected.

What behavior?

The component is supposed to allow the user to click on a button in order to enter some text through Java script interop and to update a component property in addition of displaying the value in the HTML.

Set up the component with its dependencies

To test this behavior the component needs a IJSRuntime dependency to be available. Thanks to the bUnit TestContext, we can provide all services that the component requires. In our case we will provide a Mock of the IJSRuntime service:

using var ctx = new TestContext(); // Mock JsRunTime that will return Test new value.

var jsrMock = new Mock<IJSRuntime>(); // Register the jsrMock as singleton.

ctx.Services.AddSingleton(jsrMock.Object);

This way the component will get the jsrMock in its injected properties. Since mocking class is not the purpose of this article, I won’t show how jsrMock is set up but you can find it in the test source code at the end of the article.

Tips: bUnit provides some useful helps to mock IJSRuntime (see documentation for more information).

Once the component is rendered with the right parameters, we can as usual look up the HTML elements:

// render the component

var renderedComponent = ctx.RenderComponent<Component1>(

Parameter(nameof(Component1.Text), someValue)); // Assert: first find the <text> element, then verify its initial content.

Assert.Equal(

someValue,

renderedComponent

.Find($"#{ElementIds.TextValueId}")

.TextContent);

How can we generate some events?

Now that our component is all set up and that the preliminary checks are done, we need to generate a user click event.

Once again bUnit is here to help. As we have seen before the IRenderedComponent gives us a way to find HTML elements to check contents but it also helps to generate events:

// Find and click the <button> element to set the

// text element value from the jsRuntime mock value.

await renderedComponent

.Find("button")

.ClickAsync(new MouseEventArgs());

The component is supposed to get an input from the user through the Javascript interop and display it in the HTML. There is nothing new here:

Assert.Equal(

newValue,

renderedComponent

.Find($"#{ElementIds.TextValueId}")

.TextContent);

It is also supposed to set the Text property of the component to the new input. Let’s check with the help of the IRenderedComponent instance that provides the Instance property:

// We also can inspect the component properties

// with the renderedComponent.Instance

Assert.Equal(newValue, renderedComponent.Instance.Text);

Did the component trigger its EventCallback?

Well, there is actually one last thing that could be interesting to test. Our component is supposed to trigger an EventCallback to allow the use of bidirectional binding. In our example:

// in the component class

[Parameter]

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

public EventCallback<string> TextChanged { get; set; } // In the code using the component

<Component1

ParentName="Some text"

@bind-Text="_text">

</Component1>

bUnit provides a way to setup the test component with EventCallback parameters. So we can easily test that an event is actually triggered as expected:

// define a variable to store the event value.

string textChangedEventTriggered = null; var renderedComponent = ctx.RenderComponent<Component1>(

EventCallback<string>(nameof(Component1.TextChanged),

(e) =>

{

textChangedEventTriggered = e;

})); // Find and click the <button> element.

await renderedComponent

.Find("button")

.ClickAsync(new MouseEventArgs()); // Assert that the event has been triggered with the right value

Assert.Equal(newValue, textChangedEventTriggered);

Finally, here is the full code of our test class: