IcuBlazor
Show / Hide Table of Contents

Blazor Unit Testing with IcuBlazor

Blazor allows us to enhance the practice of unit testing. Using IcuBlazor components we can create and execute

  • Traditional unit tests using True/False assertions;
  • Text and Log tests with a Visual Diff UI;
  • UI snapshot tests with pixel-based Image Diffs; and
  • End to end (E2E) integration tests with UI Automation

These tests are typically run interactively in the browser. This is especially useful with Hot Reloading. Additionally, tests can run from the command line to support CI/CD workflows.

Unit Testing

Traditional unit tests are easy to write. Here is a test suite defined in a .razor:

@page "/MyTests/TestDocsAsserts"
@inherits IcuBlazor.IcuTestSuite

@code {
    public void TestSimpleChecks() {
        Check().True(2 < 3, "a true test");
        Check().False(2 == 3, "a false test");
        Check().Equal(6*9, 42, "What's the question?");
    }

    async Task TestAsyncMethod() {
        var t0 = DateTime.Now;
        await Task.Delay(500);
        var dt = (DateTime.Now - t0).TotalMilliseconds;
        // Surprise! Sometimes dt<500 :o
        Check().True(dt > 500, $"async test waited {dt} ms");
    }
}

A suite is a subclass of IcuBlazor.IcuTestSuite. By convention, tests are defined in methods that start with "test".

To execute and view the test results you will need an <IcuTestViewer>. Here we define it in a page called /MyTests/Hello.razor.

@page "/MyTests/Hello"

<IcuTestViewer Width="1000">
    <TestDocsAsserts>
</IcuTestViewer>

Now running your app and navigating to https://localhost:.../MyTests/Hello should render something like this:

Clicking the blue links will jump to the source line in Visual Studio or VSCode. The Check() calls are used to register the line number to go to when the link is clicked.

Diff Testing

Unlike other testing tools, IcuBlazor goes beyond basic True()/False() assertions.

You can use Check().Text(expect, result, ...) and Check().File(logName, result, ...) to check long strings. If expect differs from result IcuBlazor presents a diff UI. If you click save, the result is saved in a file, {logName}.txt, and is used as the expected value the next time the test is run.

The above result was generated with the code below. Note how the test detected a change in "Summary" but there was no explicit code for that field. Consider what a traditional unit test would look like 😦. How would that test change if Summary changed 😨? Finally, consider how you would refactor the following code 😄.

void Test_text_diff()
{
    var w = new MyWeatherForecast();
    var sb = new StringBuilder();
    sb.AppendLine($"-- Weather init --");
    sb.AppendLine(Conv.AsString(w));

    w.TempC = -10;
    sb.AppendLine($"\n-- Weather @ {w.TempC}°C --");
    sb.AppendLine(Conv.AsString(w));

    w.TempC = 100;
    sb.AppendLine($"\n-- Weather @ {w.TempC}°C --");
    sb.AppendLine(Conv.AsString(w));

    Check().File("weather", sb.ToString(), "changing weather");
}

UI Testing

UI testing and automation is very easy. Here's a test suite that checks our venerable <Counter/> component.

@page "/TestExample"
@inherits IcuBlazor.IcuTestSuite

<IcuTestDiv Suite="@this" Width="300" style="background:yellow;">
    <p>Any Html or Blazor content!</p>
    <Counter @ref="myCounter"/> 
</IcuTestDiv>

@code {
    Counter myCounter;

    public async Task Test_Counter_UI()
    {
        await Check().Div("init");

        var button = await UI.Find("button", "Click me");
        await UI.Click(button);
        await UI.Click(button);
        await Check().Div("click_twice");       // Test with UI Automation!
        Check().True(myCounter.currentCount == 2, "Test internal values!");
    }
}

<IcuTestDiv> is basically a <div> that defines a test area where you can place your UI component(s). Here we have styled the div yellow just to discern its bounds. With the UI automation API, we locate the button and click it...twice! Click here for more automation methods.

Check().Div(image_file) is the "Assert" operation of UI testing. It takes a snapshot of the div and compares it pixel-by-pixel with a previous snapshot (named {image_file}.png). An image diff dialog is presented if the images differ.

Because IcuBlazor tests are Blazor components, we can use the @ref attribute to get the component object. With this reference you can directly automate your test. You can set or get state (e.g. myCounter.currentCount) or even invoke methods (e.g. myCounter.ClickButton()).

A run of this test may look like this:

Note how the counter snapshots start at 0 and end with 2, proving that the automation worked!

Additionally, the test component is still at the top of the page--live and clickable. You can continue debugging and interacting with the component even after the tests finish.

Command Line Execution

Many testing frameworks can start execution from the command line which is useful in many automation scenarios like Continuous Integration & Deployment (CI/CD). IcuBlazor can also be invoked from the shell. For example with the dotnet command:

dotnet run --icu "{view-page-command}"

{view-page-command} is any command (or even .bat file) that raises a browser with the blazor test page. For example the command

dotnet run --icu "chrome https://localhost:5001/MyTests/SimpleUIPage"

will start the app server and open a chrome browser with the specified url. The page will execute the tests and output the results to the command line. Here is a sample output:

The last line will be either OK or Fail.

Instead of chrome, you can use firefox, msedge or whatever browser you have installed. Each browser comes with useful switches that can improve the test setup. For example, the following command uses --window-size to resize the browser and --app to use a minimal frame.

dotnet run --icu "msedge --app=https://localhost:5001/MyTests/SimpleUIPage --window-size=1300,700"

See Samples/Server.SSBLinked/cmd_line_test.bat.

In This Article
Back to top Generated by DocFX