Skip to content

Releases: bUnit-dev/bUnit

Beta 7

19 May 19:30

Choose a tag to compare

NuGet downloads

The latest version of the library is available on NuGet in various incarnations:

Name Type NuGet Download Link
bUnit Library, includes core, web, and xUnit Nuget
bUnit.core Library, only core Nuget
bUnit.web Library, web and core Nuget
bUnit.xUnit Library, xUnit and core Nuget
bUnit.template Template, which currently creates an xUnit based bUnit test projects only Nuget

Contributions

Thanks to Martin Stühmer (@samtrion) and Stef Heyenrath (@StefH) for their code contributions in this release, and to Brad Wilson (@bradwilson) for his help with enabling xUnit to discover and run Razor based tests.

Also a big thank to all you who have contributed by raising issues, participated in issues by helping answer questions and providing input on design and technical issues.

New in beta 7

There are three big changes in bUnit in this release, as well as a whole host of small new features, improvements to the API, and bug fixes. The three big changes are:

  1. A splitting of the library
  2. Discovery of razor base tests, and
  3. A strongly typed way to pass parameters to a component under test.

There are also some breaking changes, which we will cover first.

NOTE: The documentation is next on the TODO list, so please bear with me while I update it to reflect all the recent changes.

Breaking changes

Due to the big restructuring of the library, there are some breaking changes, hopefully for the better.

Razor test changes

Previously, the Test and Setup methods on <Fixture> and <SnapshotTest> did not have any arguments, and the test context they represented when running, was implicitly available in the scope. This has changed with this release, such that all Test and Setup methods now receive the text context as an argument, and that should be used to call e.g. GetComponentUnderTest() on.

For example, if you have a razor based test that looks like this currently:

<Fixture Test="Test001" Setup="TestSetup">
    <ComponentUnderTest><Counter /></ComponentUnderTest>
    <Fragment>...</Fragment>
</Fixture>
@code {
    void TestSetup() => Services.AddMockJsRuntime();

    void Test001()
    {
        var cut = GetComponentUnderTest<Counter>();
        var fragment = GetFragment();
    }
}

You have to change it to this:

<Fixture Test="Test001" Setup="TestSetup">
    <ComponentUnderTest><Counter /></ComponentUnderTest>
</Fixture>
@code {
    // Add a Fixture fixture argument to the setup method and use
    // the services collection inside the fixture to register dependencies
    void TestSetup(Fixture fixture) => fixture.Services.AddMockJsRuntime();

    // Add a Fixture fixture argument to the test method
    void Test001(Fixture fixture) 
    {
        // Use the fixture instance to get the component under test
        var cut = fixture.GetComponentUnderTest<Counter>();
        var fragment = fixture.GetFragment();
    }
}

It is a little more typing, but it is also a lot more obvious what is going on, e.g. where the component under test or fragment is coming from.

In addition to this, the Tests and TestsAsync methods on <Fixture> have been deprecated in this release and throws a runtime exception if used. They were not very used and caused confusion about the state of the components under test between the method calls. Now you can only specify either a Test or TestAsync method per <Fixture>.

WaitForRender removed

The WaitForRender method has been removed entirely from the library. Since it would only wait for one render, it had a very specific use case, where as the more general WaitForAssertion or WaitForState will wait for any number of renders, until the assertion passes, or the state predicate returns true. These make them much better suited to create stable tests.

With WaitForRender, you would pass in an action that would cause a render before attempting your assertion, e.g.:

cut.WaitForRender(() => mockForecastService.Task.SetResult(forecasts));

Assert.Equal("...", cut.Markup);

This can now be changed to first call the action that will trigger the render, and then wait for an assertion to pass, using WaitForAssertion:

mockForecastService.Task.SetResult(forecasts);

cut.WaitForAssertion(() => Assert.Equal("...", cut.Markup));

The two "wait for" methods are also only available through a rendered fragment or rendered component now.

ComponentTestFixture deprecated

Previously, the recommended method for creating xUnit component test classes was to inherit from ComponentTestFixture. Due to the restructuring of the library, this type is now just a TestContext with static component parameters factory methods, so it does not add much value anymore.

The component parameter factory methods are now also available in the more general purpose ComponentParameterFactory type, which can be imported into all test classes, not just xUnit ones, using the import static Bunit.ComponentParameterFactory method, and then you can change your existing xUnit test classes to inherit from TestContext instead of ComponentTestFixture to keep the current functionality for xUnit test classes.

That covers the most important breaking changes. Now lets look at the other big changes.

Splitting up the library

In this release sees bUnit refactored and split up into three different sub libraries. The reasons for doing this are:

  • To make it possible to extract the direct dependency on xUnit and easily add support for NUnit or MSTest
  • To make it easier to maintain distinct parts of the library going forward
  • To enable future support for other non-web variants of Blazor, e.g. the Blazor Mobile Bindings.

The three parts of the library is now:

  • bUnit.core: The core library only contains code related to the general Blazor component model, i.e. it is not specific to the web version of Blazor.
  • bUnit.web: The web library, which has a dependency on core, provides all the specific types for rendering and testing Blazor web components.
  • bUnit.xUnit: The xUnit library, which has a dependency on core, has xUnit specific extensions to bUnit, that enable logging to the test output through the ILogger interface in .net core, and an extension to xUnit's test runners, that enable it to discover and run razor based tests defined in .razor files.

To keep things compatible with previous releases, an additional package is available, bUnit, which includes all of three libraries. That means existing users should be able to keep their single <PackageReference Include="bunit"> in their projects.

Discovery of Razor based tests

One of the pain points of writing Razor based tests in .razor files was that the individual tests was not correctly discovered. That meant that if had multiple tests in a file, you would not see them in Visual Studios Test Explorer individually, you could not run them individually, and error was not reported individually.

This has changed with the bUnit.xUnit library, that now includes a way for it to discover individual razor tests, currently either a <Fixture> or <SnapshotTest> inside test components defined in .razor files. It also enables you to navigate to the test by double clicking on it in the Test Explorer, and you can run each test individually, and see error reports individually.

WARNING: You still have to wait for the Blazor compiler to translate the .razor files into .cs files, before the tests show up in the Test Explorer, and the this can trip up the Test Explorer. So while this feature is a big improvement to razor based testing, it is still not perfect, and more works need to be done to refine it.

Strongly typed component parameters

If you prefer writing your tests in C# only, you will be happy to know that there is now a new strongly typed way to pass parameters to components, using a builder. E.g., to render a ContactInfo component:

var cut = RenderComponent<ContactInfo>(parameters => parameters
    .Add(p => p.Name, "Egil Hansen")
    .Add(p => p.Country, "Iceland")
);

There are a bunch of different Add methods available on the builder, that allows you to easily pass in a EventCallback, ChildContent, or RenderFragment.

The old way using the component parameter factory methods are still available if you prefer that syntax.

NOTE: The parameter builder API is experimental at this point, and will likely change.

Added

  • A new event, OnAfterRender, has been added to IRenderedFragmentBase, which IRenderedFragment inherits from. Subscribers will be invoked each time the rendered fragment is re-rendered. Related issue #118.
  • A new property, RenderCount, has been added to IRenderedFragmentBase, which IRenderedFragment inherits from. Its represents the number of times a rendered fragment has been rendered. Related issue #118.
  • A new event, OnMarkupUpdated, has been added to `IRenderedFra...
Read more

Beta 6 - introducing bUnit

01 Mar 09:18

Choose a tag to compare

This release includes a name change from Blazor Components Testing Library to bUnit. It also brings along two extra helper methods for working with asynchronously rendering components during testing, and a bunch of internal optimizations and tweaks to the code.

Why change the name? Naming is hard, and I initial chose a very product-namy name, that quite clearly stated what the library was for. However, the name isn't very searchable, since it just contains generic keywords, plus, bUnit is just much cooler. It also gave me the opportunity to remove my name from all the namespaces and simplify those.

Contributions

Hugh thanks to Rastislav Novotný (@duracellko) for his input and review of the WaitForX logic added in this release.

NuGet

The latest version of the library is availble on NuGet:

Type Link
Nuget Library https://www.nuget.org/packages/bunit/
Nuget Template https://www.nuget.org/packages/bunit.template/

Added

  • WaitForState(Func<bool> statePredicate, TimeSpan? timeout = 1 second) has been added to ITestContext and IRenderedFragment.
    This method will wait (block) until the provided statePredicate returns true, or the timeout is reached (during debugging the timeout is disabled). Each time the renderer in the test context renders, or the rendered fragment renders, the statePredicate is evaluated.

    You use this method, if you have a component under test, that requires one or more asynchronous triggered renders, to get to a desired state, before the test can continue.

    The following example tests the DelayedRenderOnClick.razor component:

    // DelayedRenderOnClick.razor
    <p>Times Clicked: @TimesClicked</p>
    <button @onclick="ClickCounter">Trigger Render</button>
    @code
    {
        public int TimesClicked { get; private set; }
    
        async Task ClickCounter()
        {
            await Task.Delay(1); // wait 1 millisecond
            TimesClicked += 1;
        }
    }

    This is a test that uses WaitForState to wait until the component under test has a desired state, before the test continues:

    [Fact]
    public void WaitForStateExample()
    {
        // Arrange
        var cut = RenderComponent<DelayedRenderOnClick>();
    
        // Act
        cut.Find("button").Click();
        cut.WaitForState(() => cut.Instance.TimesClicked == 1);
    
        // Assert
        cut.Find("p").TextContent.ShouldBe("Times Clicked: 1");
    }
  • WaitForAssertion(Action assertion, TimeSpan? timeout = 1 second) has been added to ITestContext and IRenderedFragment.
    This method will wait (block) until the provided assertion method passes, i.e. runs without throwing an assert exception, or until the timeout is reached (during debugging the timeout is disabled). Each time the renderer in the test context renders, or the rendered fragment renders, the assertion is attempted.

    You use this method, if you have a component under test, that requires one or more asynchronous triggered renders, to get to a desired state, before the assertion passes.

    This is a test that tests the DelayedRenderOnClick.razor listed above, and that uses WaitForAssertion to attempt the assertion each time the component under test renders:

    [Fact]
    public void WaitForAssertionExample()
    {
        // Arrange
        var cut = RenderComponent<DelayedRenderOnClick>();
    
        // Act
        cut.Find("button").Click();
    
        // Assert
        cut.WaitForAssertion(
            () => cut.Find("p").TextContent.ShouldBe("Times Clicked: 1")
        );
    }
  • Added support for capturing log statements from the renderer and components under test into the test output.
    To enable this, add a constructor to your test classes that takes the ITestOutputHelper as input, then in the constructor call Services.AddXunitLogger and pass the ITestOutputHelper to it, e.g.:

    // ComponentTest.cs
    public class ComponentTest : ComponentTestFixture
    {
        public ComponentTest(ITestOutputHelper output)
        {
            Services.AddXunitLogger(output, minimumLogLevel: LogLevel.Debug);
        }
    
        [Fact]
        public void Test1() ...
    }

    For Razor and Snapshot tests, the logger can be added almost the same way. The big difference is that it must be added during Setup, e.g.:

    // RazorComponentTest.razor
    <Fixture Setup="Setup" ...>
        ...
    </Fixture>
    @code {
        private ITestOutputHelper _output;
        
        public RazorComponentTest(ITestOutputHelper output)
        {
            _output = output;
        }
        
        void Setup()
        {
            Services.AddXunitLogger(_output, minimumLogLevel: LogLevel.Debug);
        }
    }
  • Added simpler Template helper method
    To make it easier to test components with RenderFragment<T> parameters (template components) in C# based tests, a new Template<TValue>(string name, Func<TValue, string> markupFactory) helper methods have been added. It allows you to create a mock template that uses the markupFactory to create the rendered markup from the template.

    This is an example of testing the SimpleWithTemplate.razor, which looks like this:

    @typeparam T
    @foreach (var d in Data)
    {
        @Template(d);      
    }
    @code
    {
        [Parameter] public RenderFragment<T> Template { get; set; }
        [Parameter] public IReadOnlyList<T> Data { get; set; } = Array.Empty<T>();
    }

    And the test code:

    var cut = RenderComponent<SimpleWithTemplate<int>>(
        ("Data", new int[] { 1, 2 }),
        Template<int>("Template", num => $"<p>{num}</p>")
    );
    
    cut.MarkupMatches("<p>1</p><p>2</p>");

    Using the more general Template helper methods, you need to write the RenderTreeBuilder logic yourself, e.g.:

    var cut = RenderComponent<SimpleWithTemplate<int>>(
        ("Data", new int[] { 1, 2 }),
        Template<int>("Template", num => builder => builder.AddMarkupContent(0, $"<p>{num}</p>"))
    );
  • Added logging to TestRenderer. To make it easier to understand the rendering life-cycle during a test, the TestRenderer will now log when ever it dispatches an event or renders a component (the log statements can be access by capturing debug logs in the test results, as mentioned above).

  • Added some of the Blazor frameworks end-2-end tests. To get better test coverage of the many rendering scenarios supported by Blazor, the ComponentRenderingTest.cs tests from the Blazor frameworks test suite has been converted from a Selenium to a bUnit. The testing style is very similar, so few changes was necessary to port the tests. The two test classes are here, if you want to compare:

Changed

  • Namespaces is now Bunit
    The namespaces have changed from Egil.RazorComponents.Testing.Library.* to simply Bunit for the library, and Bunit.Mocking.JSInterop for the JSInterop mocking support.

  • Auto-refreshing IElements returned from Find()
    IRenderedFragment.Find(string cssSelector) now returns a IElement, which internally will refresh itself, whenever the rendered fragment it was found in, changes. This means you can now search for an element once in your test and assign it to a variable, and then continue to assert against the same instance, even after triggering renders of the component under test.

    For example, instead of having cut.Find("p") in multiple places in the same test, you can do var p = cut.Find("p") once, and the use the variable p all the places you would otherwise have the Find(...) statement.

  • Refreshable element collection returned from FindAll.
    The FindAll query method on IRenderedFragment now returns a new type, the IRefreshableElementCollection<IElement> type, and the method also takes a second optional argument now, bool enableAutoRefresh = false.

    The IRefreshableElementCollection is a special collection type that can rerun the query to refresh its the collection of elements that are found by the CSS selector. This can either be done manually by calling the Refresh() method, or automatically whenever the rendered fragment renders and has changes, by setting the property EnableAutoRefresh to true (default set to false).

    Here are two example tests, that both test the following ClickAddsLi.razor component:

    <ul>
        @foreach (var x in Enumerable.Range(0, Counter))
        {
            <li>@x</li>
        }
    </ul>
    <button @onclick="() => Counter++"></button>
    @code {
        public int Counter { get; set; } = 0;
    }

    The first tests uses auto refresh, set through the optional parameter enableAutoRefresh passed to FindAll:

    public void AutoRefreshQueriesForNewElementsAutomatically()
    {
        var cut = RenderComponent<ClickAddsLi>();
        var liElements = cut.FindAll("li", enableAutoRefresh: true);
        liElements.Count.ShouldBe(0);
    
        cut.Find("button").Click();
    
        liElements.Count.ShouldBe(1...
Read more

Beta 5.1 - Fritz and Friends found a bug

24 Jan 09:16
582bc03

Choose a tag to compare

A quick point release for beta 5. The good folks over at the @FritzAndFriends Twitch stream uncovered a bug that described in #43. This has been fixed.

Ressources:

You can get the latest version from NuGet:

NuGet data Type Link
Nuget Library https://www.nuget.org/packages/Razor.Components.Testing.Library/
Nuget Template https://www.nuget.org/packages/Razor.Components.Testing.Library.Template/

Beta 5 - winter cleaning

23 Jan 09:00

Choose a tag to compare

The beta 5 release of the library is ready to go. This beta 5 comes with a few cleanups of the API, better support for adding services to the TestServiceProvider, support for async test and setup methods in snapshot and razor- based tests, and other small changes.

Since the last release, the .NET Conf - Focus on Blazor event happened, and this library was featured in a session on "Testing Blazor Components" I was invited to do. So if you are now to the library, you can get a 35 minute intro right that way. All sessions are available on YouTube, if you missed it.

Changes in beta 5:

  • Breaking change: In ITextContext, the TestHtmlParser HtmlParser { get; } property has been removed and replaced with the method INodeList CreateNodes(string markup);
  • Breaking change: The GetNodes() and GetMarkup() methods to Nodes and Markup properties in IRenderedFragment.
  • Beaking change: TestServiceProvider, exposed through the Services property in the test contexts, is now a true IServiceCollection. This unfortunately means you have to update all calls to AddService to AddSingleton (issue #39).
    NOTE: You also need to add an import to Microsoft.Extensions.DependencyInjection to make the methods AddSingleton, AddTransient, etc. available in your tests.
  • Added Async methods setup and test methods to <Fixture/>.
    Big thanks to @duracellko for the contribution of this.
  • Added a IJsRuntime placeholder to the services collection if none has been added by the developer (issue #26).
    Big thanks to @Siphonophora for the contribution of this.
  • Added SetupVoid and SetVoidResult capabilities to the built in JsRuntime mock (issue #30).
  • Moved JsRuntime Mock into its own namespace: Egil.RazorComponents.Testing.Mocking.JSInterop.

Ressources:

You can get the latest version from NuGet:

NuGet data Type Link
Nuget Library https://www.nuget.org/packages/Razor.Components.Testing.Library/
Nuget Template https://www.nuget.org/packages/Razor.Components.Testing.Library.Template/

First contributions

A big thank you to the first two contributors, Rastislav Novotný (@duracellko) and Michael J Conrad (@Siphonophora). I hope to see more in the future from you guys, as well as others.

Also thank you to those who have participated in discussion in on issues board, and provided feedback on Twitter and Gitter.

Upcoming releases

The next release will focus on making the library independent of xUnit, such that you as a developer can choose to use another library. Built-in support for e.g. nUnit might not be in the next release, but it will make it much easier to use MSTest or nUnit.

Thanks, Egil.

Beta 4: Breaking changes in tests, rendering logic, and assertions

02 Jan 22:04
f453759

Choose a tag to compare

This release include a bunch of breaking changes:

  • Renamed ShouldBe assert helper methods that performs semantic HTML comparison to MarkupMatches.
  • Upgraded to AngleSharp Diffing version 0.13.2.
  • Simplified razor based tests and snapshot tests by removing need for explicit IRazorTestContext to be passed into test methods and setup methods (#19).
  • Added a Description parameter to <Fixture> component, whose value will be displayed in the error message if the fixtures setup or test methods fails. If a description is not provided, the name of the test or setup method that caused the error is used in the error message instead.
  • Enabled RenderedFragment/RenderedComponent to detect when it and child components have updates during a render. This prevents unnecessary generating and parsing of markup to AngleSharp nodes.
  • TakeSnapshot method in IRenderFragment has been changed to SaveSnapshot
  • Added the missing event dispatcher extension methods.

Upgrading to beta 4

See the following sections for a guide to upgrading from beta-3 and eariler to beta 4.

Breaking change: Razor and Snapshot-based tests

In razor and snapshot tests, the setup and test methods no longer gets passed a IRazorTestContext. Instead, the IRazorTestContext is implicitly available in the scope of the setup and test methods, when a <Fixture> or <SnapshotTest>.

So to update your code, change all Setup and Tests methods to not take an IRazorTestContext as input. For example, a Setup like this:

void Setup(IRazorTestContext context)
{
  // Add services and do other setup work in this method.
  context.Services.AddMockJsRuntime();
}

becomes this:

void Setup()
{
  // Add services and do other setup work in this method.
  Services.AddMockJsRuntime();
}

and a Test method like this:

void Test1(IRazorTestContext context)
{
  // Renders a MyComponent component and assigns the result to
  // a cut variable. CUT is short for Component Under Test.
  var cut = context.GetComponentUnderTest<MyComponent>();

  // Renders the markup in the "first" fragment by calling GetFragment without an id.
  var firstFragment = context.GetFragment();
}

becomes this:

void Test1()
{
  // Renders a MyComponent component and assigns the result to
  // a cut variable. CUT is short for Component Under Test.
  var cut = GetComponentUnderTest<MyComponent>();

  // Renders the markup in the "first" fragment by calling GetFragment without an id.
  var firstFragment = GetFragment();
}

Semantic HTML assert helper ShouldBe changed to MarkupMatches

To make it more clear what is being verified in tests, the more generic sounding semantic HTML verifying assert method ShouldBe has been renamed to MarkupMatches.

For example, the following test:

[Fact]
public void InitialHtmlIsCorrect()
{
    var cut = RenderComponent<Counter>();

    var expectedHtml = @"<h1>Counter</h1>
                        <p>Current count: 0</p>
                        <button class=""btn-primary btn"">Click me</button>";

    cut.ShouldBe(expectedHtml); // OLD CODE HERE
}

has to be updated to this:

[Fact]
public void InitialHtmlIsCorrect()
{
    var cut = RenderComponent<Counter>();

    var expectedHtml = @"<h1>Counter</h1>
                        <p>Current count: 0</p>
                        <button class=""btn-primary btn"">Click me</button>";

    cut.MarkupMatches(expectedHtml); // NEW CODE HERE
}

TakeSnapshot changed to SaveSnapshot in IRenderFragment

I wasn't happy with the word Take in TakeSnapshot. The word Save seems more appropriate. Thus, if you are using this feature, you need to update all calls to cut.TakeSnapshot() to cut.SaveSnapshot().

1.0.0 beta-3: Better error handling and extra helper methods

20 Dec 13:38

Choose a tag to compare

This release include the following:

  • Better error handling when rendering components, with better exception messages to help discover what has gone wrong.
  • Added extra component parameter helper methods.
  • Clean up of unnecessary methods in the ITextContext interface.
  • Major upgrades to docs. Moved to the Wiki. Hopefully with better getting started section.

1.0.0-beta-2: Support for .net aspcore 3.1

15 Dec 08:31

Choose a tag to compare

This is a major, breaking upgrade, to the compared with the previous versions of the library.

It includes:

  • Combine Steve Sandersons testing prototype into the library. Many of his ideas have been incorporated with some changes to API.
  • Utilize my AngleSharp.Diffing library instead of XmlUnit to do semantic HTML comparisons.
  • Many changes to API and functionality, please see the examples for how to continue.

As always, feedback is very welcome.

1.0.0 beta 1 release

13 Dec 14:52
385fb27

Choose a tag to compare

1.0.0 beta 1 release Pre-release
Pre-release

TODO: Write change logs and update readme

Support for AspNetCore 3.1.0-preview1

17 Oct 14:50

Choose a tag to compare

Upgraded to support AspNetCore 3.0.0

23 Sep 17:11

Choose a tag to compare

Pre-release

This is small release that adds support for AspNetCore 3.0.0.

Nuget package available here: https://www.nuget.org/packages/Razor.Components.Testing.Library/