Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit Testing a Blazor RenderFragment element

Tags:

c#

xunit

blazor

I've started to write a method for dynamically building a RenderFragment Element. As such I'm also trying to write Unit Tests alongside the method.

I'm starting with a very basic element and it fails. Here's the concrete method under test:

public RenderFragment buildFragment(string element, string elementContent, string[] attribute, string[] attributeContent)
    {
        RenderFragment content = builder => {
            builder.OpenElement(0, element);
            if (attribute != null)
            {
                for (int i = 0; attribute.Length - 1 >= i; ++i)
                {
                    builder.AddAttribute(0, attribute[i], attributeContent[i]);
                }
            }
            if (!string.IsNullOrEmpty(elementContent))
            {
                builder.AddContent(0, elementContent);
            }
            builder.CloseElement();
        };

        return content;
    }

This is my first basic test against the method using xUnit:

public void BuildFragmentReturnsOneElement()
        {
            //Arrange
            RenderFragment fragment = builder =>
            {
                builder.OpenElement(0, "p");
                builder.CloseElement();
            };

            //Act
            RenderFragment result = _dynamicContentHelper.buildFragment("p", string.Empty, null, null);

            //Assert
            Assert.Same(fragment, result);
        }

The error I receive is:

Message: Assert.Same() Failure Expected: RenderFragment { Method = Void b__2_0(Microsoft.AspNetCore.Blazor.RenderTree.RenderTreeBuilder), Target = <>c { } } Actual: RenderFragment { Method = Void b__0(Microsoft.AspNetCore.Blazor.RenderTree.RenderTreeBuilder), Target = <>c__DisplayClass0_0 { attribute = null, attributeContent = null, element = "p", elementContent = "" } }

I don't understand why Target on my fragment object is different from the Target on result.

like image 316
bilpor Avatar asked Jan 22 '19 12:01

bilpor


2 Answers

The RenderFragment is a Delegate method, so when you write code like this:

RenderFragment fragment = builder =>
            {
                builder.OpenElement(0, "p");
                builder.CloseElement();
            };

You are not creating a materialised artifact, but declaring a delegate that can be invoked.

The code Assert.Same(fragment, result); is therefore comparing two delegates, which are clearly not the same - they point to two different methods.

I believe you should investigate the "test" folder of the Blazor Source

This section may help a lot

The technique they apply is to examine the frames of the RenderTree

// Act
var frames = GetRenderTree(component);

// Assert
Assert.Collection(
 frames,
 frame => AssertFrame.Component(frame, "Test.RenderChildContent", 2, 0),
 frame => AssertFrame.Attribute(frame, RenderTreeBuilder.ChildContent, 1),
 frame => AssertFrame.Markup(frame, "\n  <div></div>\n", 2));

Also, this section with a TestRenderer which has this code in it

protected RenderTreeFrame[] GetRenderTree(IComponent component)
        {
            var renderer = new TestRenderer();
            renderer.AttachComponent(component);
            component.SetParameters(ParameterCollection.Empty);
            return renderer.LatestBatchReferenceFrames;
        }

Have a look at how they do their tests as I cannot reproduce it all here, but those are the keys...

like image 134
Mister Magoo Avatar answered Oct 13 '22 21:10

Mister Magoo


I found that there is a very useful library for unit testing Blazor components named bUnit library. The tests are very simple to be written. Here is an example of validating that a button click is working.

[Fact]
public void TestCounter()
{
    // Arrange
    var cut = RenderComponent<Counter>();
    cut.Find("p").MarkupMatches("<p>Current count: 0</p>");

    // Act
    var element = cut.Find("button");
    element.Click();

    //Assert
    cut.Find("p").MarkupMatches("<p>Current count: 1</p>");
}

And here is another example which includes mocking of service with JustMock Lite

[Fact]
public void TestFetchData_ForecastIsNull()
{
    // Arrange
    var weatherForecastServiceMock = Mock.Create<IWeatherForecastService>();
    Mock.Arrange(() => weatherForecastServiceMock.GetForecastAsync(Arg.IsAny<DateTime>()))
        .Returns(new TaskCompletionSource<WeatherForecast[]>().Task);
    Services.AddSingleton<IWeatherForecastService>(weatherForecastServiceMock);

    // Act
    var cut = RenderComponent<FetchData>();

    // Assert - that it renders the initial loading message
    var initialExpectedHtml = 
                @"<h1>Weather forecast</h1>
                <p>This component demonstrates fetching data from a service.</p>
                <p><em>Loading...</em></p>";
    cut.MarkupMatches(initialExpectedHtml);
}

The examples are from the blog post Unit Testing Blazor Components with bUnit and JustMock

like image 38
Mihail Vladov Avatar answered Oct 13 '22 21:10

Mihail Vladov