|
| 1 | +# Changelog |
| 2 | +All notable changes to **bUnit** will be documented in this file. The project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). |
| 3 | + |
| 4 | +## [Unreleased] |
| 5 | +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. |
| 6 | + |
| 7 | +*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. |
| 8 | + |
| 9 | +### NuGet |
| 10 | +The latest version of the library is availble on NuGet: |
| 11 | + |
| 12 | +| | Type | Link | |
| 13 | +| ------------- | ----- | ---- | |
| 14 | +| [](https://www.nuget.org/packages/bunit/) | Library | [https://www.nuget.org/packages/bunit/](https://www.nuget.org/packages/bunit/) | |
| 15 | +| [](https://www.nuget.org/packages/bunit.template/) | Template | [https://www.nuget.org/packages/bunit.template/](https://www.nuget.org/packages/bunit.template/) | |
| 16 | + |
| 17 | +### Added |
| 18 | +- **`WaitForState(Func<bool> statePredicate, TimeSpan? timeout = 1 second)` has been added to `ITestContext` and `IRenderedFragment`.** |
| 19 | + 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. |
| 20 | + |
| 21 | + 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. |
| 22 | + |
| 23 | + The following example tests the `DelayedRenderOnClick.razor` component: |
| 24 | + |
| 25 | + ```cshtml |
| 26 | + // DelayedRenderOnClick.razor |
| 27 | + <p>Times Clicked: @TimesClicked</p> |
| 28 | + <button @onclick="ClickCounter">Trigger Render</button> |
| 29 | + @code |
| 30 | + { |
| 31 | + public int TimesClicked { get; private set; } |
| 32 | + |
| 33 | + async Task ClickCounter() |
| 34 | + { |
| 35 | + await Task.Delay(1); // wait 1 millisecond |
| 36 | + TimesClicked += 1; |
| 37 | + } |
| 38 | + } |
| 39 | + ``` |
| 40 | + |
| 41 | + This is a test that uses `WaitForState` to wait until the component under test has a desired state, before the test continues: |
| 42 | + |
| 43 | + ```csharp |
| 44 | + [Fact] |
| 45 | + public void WaitForStateExample() |
| 46 | + { |
| 47 | + // Arrange |
| 48 | + var cut = RenderComponent<DelayedRenderOnClick>(); |
| 49 | + |
| 50 | + // Act |
| 51 | + cut.Find("button").Click(); |
| 52 | + cut.WaitForState(() => cut.Instance.TimesClicked == 1); |
| 53 | + |
| 54 | + // Assert |
| 55 | + cut.Find("p").TextContent.ShouldBe("Times Clicked: 1"); |
| 56 | + } |
| 57 | + ``` |
| 58 | + |
| 59 | +- **`WaitForAssertion(Action assertion, TimeSpan? timeout = 1 second)` has been added to `ITestContext` and `IRenderedFragment`.** |
| 60 | + 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. |
| 61 | + |
| 62 | + 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. |
| 63 | + |
| 64 | + 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: |
| 65 | + |
| 66 | + ```csharp |
| 67 | + [Fact] |
| 68 | + public void WaitForAssertionExample() |
| 69 | + { |
| 70 | + // Arrange |
| 71 | + var cut = RenderComponent<DelayedRenderOnClick>(); |
| 72 | + |
| 73 | + // Act |
| 74 | + cut.Find("button").Click(); |
| 75 | + |
| 76 | + // Assert |
| 77 | + cut.WaitForAssertion( |
| 78 | + () => cut.Find("p").TextContent.ShouldBe("Times Clicked: 1") |
| 79 | + ); |
| 80 | + } |
| 81 | + ``` |
| 82 | + |
| 83 | +- **Added support for capturing log statements from the renderer and components under test into the test output.** |
| 84 | + 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.: |
| 85 | + |
| 86 | + ```csharp |
| 87 | + // ComponentTest.cs |
| 88 | + public class ComponentTest : ComponentTestFixture |
| 89 | + { |
| 90 | + public ComponentTest(ITestOutputHelper output) |
| 91 | + { |
| 92 | + Services.AddXunitLogger(output, minimumLogLevel: LogLevel.Debug); |
| 93 | + } |
| 94 | + |
| 95 | + [Fact] |
| 96 | + public void Test1() ... |
| 97 | + } |
| 98 | + ``` |
| 99 | + |
| 100 | + 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.: |
| 101 | + |
| 102 | + ```cshtml |
| 103 | + // RazorComponentTest.razor |
| 104 | + <Fixture Setup="Setup" ...> |
| 105 | + ... |
| 106 | + </Fixture> |
| 107 | + @code { |
| 108 | + private ITestOutputHelper _output; |
| 109 | + |
| 110 | + public RazorComponentTest(ITestOutputHelper output) |
| 111 | + { |
| 112 | + _output = output; |
| 113 | + } |
| 114 | + |
| 115 | + void Setup() |
| 116 | + { |
| 117 | + Services.AddXunitLogger(_output, minimumLogLevel: LogLevel.Debug); |
| 118 | + } |
| 119 | + } |
| 120 | + ``` |
| 121 | + |
| 122 | +- **Added simpler `Template` helper method** |
| 123 | + 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. |
| 124 | + |
| 125 | + This is an example of testing the `SimpleWithTemplate.razor`, which looks like this: |
| 126 | + |
| 127 | + ```cshtml |
| 128 | + @typeparam T |
| 129 | + @foreach (var d in Data) |
| 130 | + { |
| 131 | + @Template(d); |
| 132 | + } |
| 133 | + @code |
| 134 | + { |
| 135 | + [Parameter] public RenderFragment<T> Template { get; set; } |
| 136 | + [Parameter] public IReadOnlyList<T> Data { get; set; } = Array.Empty<T>(); |
| 137 | + } |
| 138 | + ``` |
| 139 | + |
| 140 | + And the test code: |
| 141 | + |
| 142 | + ```csharp |
| 143 | + var cut = RenderComponent<SimpleWithTemplate<int>>( |
| 144 | + ("Data", new int[] { 1, 2 }), |
| 145 | + Template<int>("Template", num => $"<p>{num}</p>") |
| 146 | + ); |
| 147 | + |
| 148 | + cut.MarkupMatches("<p>1</p><p>2</p>"); |
| 149 | + ``` |
| 150 | + |
| 151 | + Using the more general `Template` helper methods, you need to write the `RenderTreeBuilder` logic yourself, e.g.: |
| 152 | + |
| 153 | + ```csharp |
| 154 | + var cut = RenderComponent<SimpleWithTemplate<int>>( |
| 155 | + ("Data", new int[] { 1, 2 }), |
| 156 | + Template<int>("Template", num => builder => builder.AddMarkupContent(0, $"<p>{num}</p>")) |
| 157 | + ); |
| 158 | + ``` |
| 159 | + |
| 160 | +- **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). |
| 161 | + |
| 162 | +- **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](https://github.com/dotnet/aspnetcore/blob/master/src/Components/test/E2ETest/Tests/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: |
| 163 | + |
| 164 | + - [bUnit's ComponentRenderingTest.cs](/master/tests/BlazorE2E/ComponentRenderingTest.cs) |
| 165 | + - [Blazor's ComponentRenderingTest.cs](https://github.com/dotnet/aspnetcore/blob/master/src/Components/test/E2ETest/Tests/ComponentRenderingTest.cs) |
| 166 | + |
| 167 | +### Changed |
| 168 | +- **Namespaces is now `Bunit`** |
| 169 | + The namespaces have changed from `Egil.RazorComponents.Testing.Library.*` to simply `Bunit` for the library, and `Bunit.Mocking.JSInterop` for the JSInterop mocking support. |
| 170 | + |
| 171 | +- **Auto-refreshing `IElement`s returned from `Find()`** |
| 172 | + `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. |
| 173 | + |
| 174 | + 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. |
| 175 | + |
| 176 | +- **Refreshable element collection returned from `FindAll`.** |
| 177 | + 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`. |
| 178 | + |
| 179 | + 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`). |
| 180 | + |
| 181 | + Here are two example tests, that both test the following `ClickAddsLi.razor` component: |
| 182 | + |
| 183 | + ```cshtml |
| 184 | + <ul> |
| 185 | + @foreach (var x in Enumerable.Range(0, Counter)) |
| 186 | + { |
| 187 | + <li>@x</li> |
| 188 | + } |
| 189 | + </ul> |
| 190 | + <button @onclick="() => Counter++"></button> |
| 191 | + @code { |
| 192 | + public int Counter { get; set; } = 0; |
| 193 | + } |
| 194 | + ``` |
| 195 | + |
| 196 | + The first tests uses auto refresh, set through the optional parameter `enableAutoRefresh` passed to FindAll: |
| 197 | + |
| 198 | + ```csharp |
| 199 | + public void AutoRefreshQueriesForNewElementsAutomatically() |
| 200 | + { |
| 201 | + var cut = RenderComponent<ClickAddsLi>(); |
| 202 | + var liElements = cut.FindAll("li", enableAutoRefresh: true); |
| 203 | + liElements.Count.ShouldBe(0); |
| 204 | + |
| 205 | + cut.Find("button").Click(); |
| 206 | + |
| 207 | + liElements.Count.ShouldBe(1); |
| 208 | + } |
| 209 | + ``` |
| 210 | + |
| 211 | + The second test refreshes the collection manually through the `Refresh()` method on the collection: |
| 212 | + |
| 213 | + ```csharp |
| 214 | + public void RefreshQueriesForNewElements() |
| 215 | + { |
| 216 | + var cut = RenderComponent<ClickAddsLi>(); |
| 217 | + var liElements = cut.FindAll("li"); |
| 218 | + liElements.Count.ShouldBe(0); |
| 219 | + |
| 220 | + cut.Find("button").Click(); |
| 221 | + |
| 222 | + liElements.Refresh(); // Refresh the collection |
| 223 | + liElements.Count.ShouldBe(1); |
| 224 | + } |
| 225 | + ``` |
| 226 | + |
| 227 | +- **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 `MissingEventHandlerException` exception, instead of an `ArgumentException`. |
| 228 | + |
| 229 | +### Deprecated |
| 230 | +- **`WaitForNextRender` has been deprecated (marked as obsolete)**, since the added `WaitForState` and `WaitForAssertion` provide 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. |
| 231 | + |
| 232 | +### Removed |
| 233 | +- **`AddMockHttp` and related helper methods have been removed.** |
| 234 | + The mocking of HTTPClient, supported through the [mockhttp](https://github.com/richardszalay/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. |
| 235 | + |
| 236 | + If you are using mockhttp, you can easily add again to your testing project. See [TODO Guide to mocking HttpClient](#) in the docs to learn how. |
| 237 | + |
| 238 | +### Fixed |
| 239 | +- **Wrong casing on keyboard event dispatch helpers.** |
| 240 | + The helper methods for the keyboard events was not probably cased, so that has been updated. E.g. from `Keypress(...)` to `KeyPress(...)`. |
0 commit comments