Beta 6 - introducing bUnit
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 | |
|---|---|---|
| Library | https://www.nuget.org/packages/bunit/ | |
| Template | https://www.nuget.org/packages/bunit.template/ | 
Added
- 
WaitForState(Func<bool> statePredicate, TimeSpan? timeout = 1 second)has been added toITestContextandIRenderedFragment.
 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.razorcomponent:// 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 WaitForStateto 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 toITestContextandIRenderedFragment.
 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.razorlisted above, and that usesWaitForAssertionto 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 theITestOutputHelperas input, then in the constructor callServices.AddXunitLoggerand pass theITestOutputHelperto 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 Templatehelper method
 To make it easier to test components withRenderFragment<T>parameters (template components) in C# based tests, a newTemplate<TValue>(string name, Func<TValue, string> markupFactory)helper methods have been added. It allows you to create a mock template that uses themarkupFactoryto 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 Templatehelper methods, you need to write theRenderTreeBuilderlogic 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 TestRendererwill 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 fromEgil.RazorComponents.Testing.Library.*to simplyBunitfor the library, andBunit.Mocking.JSInteropfor the JSInterop mocking support.
- 
Auto-refreshing IElements returned fromFind()
 IRenderedFragment.Find(string cssSelector)now returns aIElement, 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 dovar p = cut.Find("p")once, and the use the variablepall the places you would otherwise have theFind(...)statement.
- 
Refreshable element collection returned from FindAll.
 TheFindAllquery method onIRenderedFragmentnow returns a new type, theIRefreshableElementCollection<IElement>type, and the method also takes a second optional argument now,bool enableAutoRefresh = false.The IRefreshableElementCollectionis 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 theRefresh()method, or automatically whenever the rendered fragment renders and has changes, by setting the propertyEnableAutoRefreshtotrue(default set tofalse).Here are two example tests, that both test the following ClickAddsLi.razorcomponent:<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 enableAutoRefreshpassed 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); } The second test refreshes the collection manually through the Refresh()method on the collection:public void RefreshQueriesForNewElements() { var cut = RenderComponent<ClickAddsLi>(); var liElements = cut.FindAll("li"); liElements.Count.ShouldBe(0); cut.Find("button").Click(); liElements.Refresh(); // Refresh the collection liElements.Count.ShouldBe(1); } 
- 
Custom exception when event handler is missing. Attempting to triggering a event handler on an element which does not have an handler attached now throws a MissingEventHandlerExceptionexception, instead of anArgumentException.
Deprecated
- WaitForNextRenderhas been deprecated (marked as obsolete), since the added- WaitForStateand- WaitForAssertionprovide a much better foundation to build stable tests on. The plan is to remove completely from the library with the final 1.0.0 release.
Removed
- 
AddMockHttpand related helper methods have been removed.
 The mocking of HTTPClient, supported through the mockhttp library, has been removed from the library. This was done because the library really shouldn't have a dependency on a 3. party mocking library. It adds maintenance overhead and uneeded dependencies to it.If you are using mockhttp, you can easily add again to your testing project. See the related issue for an example of how. 
Fixed
- Wrong casing on keyboard event dispatch helpers.
 The helper methods for the keyboard events was not probably cased, so that has been updated. E.g. fromKeypress(...)toKeyPress(...).