Skip to content

Commit eeceb3e

Browse files
authored
feat: WaitFor methods exceptions include the number of times a check was performed before a timeout
WaitFor methods exceptions include the number of times a check was performed before a timeout * fix: track total render cycles * fix: track rendered fragment render count during wait for * fix: extend timeout for slow tests
1 parent 0c0aa69 commit eeceb3e

File tree

8 files changed

+50
-22
lines changed

8 files changed

+50
-22
lines changed

src/bunit.core/Extensions/WaitForHelpers/WaitForFailedException.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using System.ComponentModel;
2+
13
namespace Bunit.Extensions.WaitForHelpers;
24

35
/// <summary>
@@ -14,6 +16,11 @@ public WaitForFailedException(string? errorMessage, Exception? innerException =
1416
{
1517
}
1618

19+
internal WaitForFailedException(string errorMessage, int checkCount, int componentRenderCount, int totalRenderCount, Exception? innerException = null)
20+
: base(errorMessage + $" Check count: {checkCount}. Component render count: {componentRenderCount}. Total render count: {totalRenderCount}.", innerException)
21+
{
22+
}
23+
1724
private WaitForFailedException(SerializationInfo info, StreamingContext context)
1825
: base(info, context) { }
1926
}

src/bunit.core/Extensions/WaitForHelpers/WaitForHelper.cs

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ public abstract class WaitForHelper<T> : IDisposable
1414
private readonly Func<(bool CheckPassed, T Content)> completeChecker;
1515
private readonly IRenderedFragmentBase renderedFragment;
1616
private readonly ILogger<WaitForHelper<T>> logger;
17+
private readonly TestRenderer renderer;
1718
private bool isDisposed;
19+
private int checkCount;
1820
private Exception? capturedException;
1921

2022
/// <summary>
@@ -49,15 +51,24 @@ protected WaitForHelper(
4951
{
5052
this.renderedFragment = renderedFragment ?? throw new ArgumentNullException(nameof(renderedFragment));
5153
this.completeChecker = completeChecker ?? throw new ArgumentNullException(nameof(completeChecker));
52-
54+
5355
logger = renderedFragment.Services.CreateLogger<WaitForHelper<T>>();
56+
renderer = (TestRenderer)renderedFragment
57+
.Services
58+
.GetRequiredService<ITestRenderer>();
5459
checkPassedCompletionSource = new TaskCompletionSource<T>(TaskCreationOptions.RunContinuationsAsynchronously);
5560
timer = new Timer(_ =>
5661
{
5762
logger.LogWaiterTimedOut(renderedFragment.ComponentId);
58-
checkPassedCompletionSource.TrySetException(new WaitForFailedException(TimeoutErrorMessage, capturedException));
63+
checkPassedCompletionSource.TrySetException(
64+
new WaitForFailedException(
65+
TimeoutErrorMessage ?? string.Empty,
66+
checkCount,
67+
renderedFragment.RenderCount,
68+
renderer.RenderCount,
69+
capturedException));
5970
});
60-
WaitTask = CreateWaitTask(renderedFragment);
71+
WaitTask = CreateWaitTask();
6172
timer.Change(GetRuntimeTimeout(timeout), Timeout.InfiniteTimeSpan);
6273

6374
InitializeWaiting();
@@ -113,12 +124,8 @@ private void InitializeWaiting()
113124
}
114125
}
115126

116-
private Task<T> CreateWaitTask(IRenderedFragmentBase renderedFragment)
117-
{
118-
var renderer = renderedFragment
119-
.Services
120-
.GetRequiredService<ITestRenderer>();
121-
127+
private Task<T> CreateWaitTask()
128+
{
122129
// Two to failure conditions, that the renderer captures an unhandled
123130
// exception from a component or itself, or that the timeout is reached,
124131
// are executed on the renderers scheduler, to ensure that OnAfterRender
@@ -144,6 +151,7 @@ private void OnAfterRender(object? sender, EventArgs args)
144151
logger.LogCheckingWaitCondition(renderedFragment.ComponentId);
145152

146153
var checkResult = completeChecker();
154+
checkCount++;
147155
if (checkResult.CheckPassed)
148156
{
149157
checkPassedCompletionSource.TrySetResult(checkResult.Content);
@@ -157,13 +165,19 @@ private void OnAfterRender(object? sender, EventArgs args)
157165
}
158166
catch (Exception ex)
159167
{
168+
checkCount++;
160169
capturedException = ex;
161170
logger.LogCheckThrow(renderedFragment.ComponentId, ex);
162171

163172
if (StopWaitingOnCheckException)
164173
{
165174
checkPassedCompletionSource.TrySetException(
166-
new WaitForFailedException(CheckThrowErrorMessage, capturedException));
175+
new WaitForFailedException(
176+
CheckThrowErrorMessage ?? string.Empty,
177+
checkCount,
178+
renderedFragment.RenderCount,
179+
renderer.RenderCount,
180+
capturedException));
167181
Dispose();
168182
}
169183
}

src/bunit.core/Rendering/TestRenderer.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ public class TestRenderer : Renderer, ITestRenderer
2121
/// <inheritdoc/>
2222
public override Dispatcher Dispatcher { get; } = Dispatcher.CreateDefault();
2323

24+
/// <summary>
25+
/// Gets the number of render cycles that has been performed.
26+
/// </summary>
27+
internal int RenderCount { get; private set; }
28+
2429
/// <summary>
2530
/// Initializes a new instance of the <see cref="TestRenderer"/> class.
2631
/// </summary>
@@ -151,6 +156,8 @@ protected override Task UpdateDisplayAsync(in RenderBatch renderBatch)
151156
{
152157
logger.LogNewRenderBatchReceived();
153158

159+
RenderCount++;
160+
154161
var renderEvent = new RenderEvent(renderBatch, new RenderTreeFrameDictionary());
155162

156163
// removes disposed components

tests/bunit.core.tests/Extensions/WaitForHelpers/RenderedFragmentWaitForHelperExtensionsTest.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public void Test011()
3636
var expected = Should.Throw<WaitForFailedException>(() =>
3737
cut.WaitForAssertion(() => cut.Markup.ShouldBeEmpty(), TimeSpan.FromMilliseconds(10)));
3838

39-
expected.Message.ShouldBe(WaitForAssertionHelper.TimeoutMessage);
39+
expected.Message.ShouldStartWith(WaitForAssertionHelper.TimeoutMessage);
4040
}
4141

4242
[Fact(DisplayName = "WaitForState throws exception after timeout")]
@@ -47,7 +47,7 @@ public void Test012()
4747
var expected = Should.Throw<WaitForFailedException>(() =>
4848
cut.WaitForState(() => string.IsNullOrEmpty(cut.Markup), TimeSpan.FromMilliseconds(100)));
4949

50-
expected.Message.ShouldBe(WaitForStateHelper.TimeoutBeforePassMessage);
50+
expected.Message.ShouldStartWith(WaitForStateHelper.TimeoutBeforePassMessage);
5151
}
5252

5353
[Fact(DisplayName = "WaitForState throws exception if statePredicate throws on a later render")]
@@ -66,7 +66,7 @@ public void Test013()
6666
return false;
6767
}));
6868

69-
expected.Message.ShouldBe(WaitForStateHelper.ExceptionInPredicateMessage);
69+
expected.Message.ShouldStartWith(WaitForStateHelper.ExceptionInPredicateMessage);
7070
expected.InnerException.ShouldBeOfType<InvalidOperationException>()
7171
.Message.ShouldBe(expectedInnerMessage);
7272
}

tests/bunit.core.tests/TestContextBaseTest.net5.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ public async Task Test202()
6666

6767
DisposeComponents();
6868

69-
await wasDisposedTask.ShouldCompleteWithin(TimeSpan.FromMilliseconds(100));
69+
await wasDisposedTask.ShouldCompleteWithin(TimeSpan.FromSeconds(1));
7070
}
7171

7272
[Fact(DisplayName = "DisposeComponents should dispose components added via ComponentFactory")]

tests/bunit.web.tests/BlazorE2E/ComponentRenderingTest.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -544,7 +544,7 @@ public async Task CanAcceptSimultaneousRenderRequests()
544544

545545
await cut.WaitForAssertionAsync(
546546
() => Assert.Equal(expectedOutput, outputElement.TextContent.Trim()),
547-
timeout: TimeSpan.FromMilliseconds(2000));
547+
timeout: TimeSpan.FromSeconds(10));
548548
}
549549

550550
[Fact]
@@ -565,7 +565,7 @@ public void CanAcceptSimultaneousRenderRequests_Sync()
565565

566566
cut.WaitForAssertion(
567567
() => Assert.Equal(expectedOutput, outputElement.TextContent.Trim()),
568-
timeout: TimeSpan.FromMilliseconds(2000));
568+
timeout: TimeSpan.FromSeconds(10));
569569
}
570570

571571
[Fact]

tests/bunit.web.tests/Extensions/WaitForHelpers/RenderedFragmentWaitForElementsHelperExtensions.Async.Test.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public async Task Test002()
3131
var expected = await Should.ThrowAsync<WaitForFailedException>(async () =>
3232
await cut.WaitForElementAsync("#notHereElm", TimeSpan.FromMilliseconds(10)));
3333

34-
expected.Message.ShouldBe(WaitForElementHelper.TimeoutBeforeFoundMessage);
34+
expected.Message.ShouldStartWith(WaitForElementHelper.TimeoutBeforeFoundMessage);
3535
}
3636

3737
[Fact(DisplayName = "WaitForElements waits until cssSelector returns at least one element")]
@@ -55,7 +55,7 @@ public async Task Test022()
5555
var expected = await Should.ThrowAsync<WaitForFailedException>(async () =>
5656
await cut.WaitForElementsAsync("#notHereElm", TimeSpan.FromMilliseconds(30)));
5757

58-
expected.Message.ShouldBe(WaitForElementsHelper.TimeoutBeforeFoundMessage);
58+
expected.Message.ShouldStartWith(WaitForElementsHelper.TimeoutBeforeFoundMessage);
5959
expected.InnerException.ShouldBeNull();
6060
}
6161

@@ -68,7 +68,7 @@ public async Task Test023()
6868
var expected = await Should.ThrowAsync<WaitForFailedException>(async () =>
6969
await cut.WaitForElementsAsync("#notHereElm", 2, TimeSpan.FromMilliseconds(30)));
7070

71-
expected.Message.ShouldBe(string.Format(CultureInfo.InvariantCulture, WaitForElementsHelper.TimeoutBeforeFoundWithCountMessage, 2));
71+
expected.Message.ShouldStartWith(string.Format(CultureInfo.InvariantCulture, WaitForElementsHelper.TimeoutBeforeFoundWithCountMessage, 2));
7272
expected.InnerException.ShouldBeNull();
7373
}
7474

tests/bunit.web.tests/Extensions/WaitForHelpers/RenderedFragmentWaitForElementsHelperExtensionsTest.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public void Test002()
3131
var expected = Should.Throw<WaitForFailedException>(() =>
3232
cut.WaitForElement("#notHereElm", TimeSpan.FromMilliseconds(10)));
3333

34-
expected.Message.ShouldBe(WaitForElementHelper.TimeoutBeforeFoundMessage);
34+
expected.Message.ShouldStartWith(WaitForElementHelper.TimeoutBeforeFoundMessage);
3535
}
3636

3737
[Fact(DisplayName = "WaitForElements waits until cssSelector returns at least one element")]
@@ -55,7 +55,7 @@ public void Test022()
5555
var expected = Should.Throw<WaitForFailedException>(() =>
5656
cut.WaitForElements("#notHereElm", TimeSpan.FromMilliseconds(30)));
5757

58-
expected.Message.ShouldBe(WaitForElementsHelper.TimeoutBeforeFoundMessage);
58+
expected.Message.ShouldStartWith(WaitForElementsHelper.TimeoutBeforeFoundMessage);
5959
expected.InnerException.ShouldBeNull();
6060
}
6161

@@ -68,7 +68,7 @@ public void Test023()
6868
var expected = Should.Throw<WaitForFailedException>(() =>
6969
cut.WaitForElements("#notHereElm", 2, TimeSpan.FromMilliseconds(30)));
7070

71-
expected.Message.ShouldBe(string.Format(CultureInfo.InvariantCulture, WaitForElementsHelper.TimeoutBeforeFoundWithCountMessage, 2));
71+
expected.Message.ShouldStartWith(string.Format(CultureInfo.InvariantCulture, WaitForElementsHelper.TimeoutBeforeFoundWithCountMessage, 2));
7272
expected.InnerException.ShouldBeNull();
7373
}
7474

0 commit comments

Comments
 (0)