Skip to content

Commit 186eeef

Browse files
committed
Big refactor of razor tests
1 parent 3e7a322 commit 186eeef

48 files changed

Lines changed: 792 additions & 717 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/bunit.core/Extensions/WaitForExtensions/RenderWaitingHelperExtensions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ public static void WaitForAssertion(this ITestContext testContext, Action assert
6666
/// <param name="timeout">The maximum time to wait for the desired state.</param>
6767
/// <exception cref="ArgumentNullException">Thrown if <paramref name="renderedFragment"/> is null.</exception>
6868
/// <exception cref="WaitForStateFailedException">Thrown if the <paramref name="statePredicate"/> throw an exception during invocation, or if the timeout has been reached. See the inner exception for details.</exception>
69-
public static void WaitForState(this IRenderedFragment renderedFragment, Func<bool> statePredicate, TimeSpan? timeout = null)
69+
public static void WaitForState(this IRenderedFragmentCore renderedFragment, Func<bool> statePredicate, TimeSpan? timeout = null)
7070
=> WaitForState(renderedFragment.RenderEvents, statePredicate, timeout);
7171

7272
/// <summary>
@@ -81,7 +81,7 @@ public static void WaitForState(this IRenderedFragment renderedFragment, Func<bo
8181
/// <param name="timeout">The maximum time to attempt the verification.</param>
8282
/// <exception cref="ArgumentNullException">Thrown if <paramref name="renderedFragment"/> is null.</exception>
8383
/// <exception cref="WaitForAssertionFailedException">Thrown if the timeout has been reached. See the inner exception to see the captured assertion exception.</exception>
84-
public static void WaitForAssertion(this IRenderedFragment renderedFragment, Action assertion, TimeSpan? timeout = null)
84+
public static void WaitForAssertion(this IRenderedFragmentCore renderedFragment, Action assertion, TimeSpan? timeout = null)
8585
=> WaitForAssertion(renderedFragment.RenderEvents, assertion, timeout);
8686

8787
private static void WaitForRender(IObservable<RenderEvent> renderEventObservable, Action? renderTrigger = null, TimeSpan? timeout = null)
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ namespace Bunit
77
/// Represents a rendered component-under-test.
88
/// </summary>
99
/// <typeparam name="TComponent">The type of the component under test</typeparam>
10-
public interface IRenderedComponent<out TComponent> : IRenderedFragment where TComponent : IComponent
10+
public interface IRenderedComponentCore<out TComponent> : IRenderedFragmentCore where TComponent : IComponent
1111
{
1212
/// <summary>
1313
/// Gets the component under test

src/bunit.core/IRenderedFragment.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ namespace Bunit
66
/// <summary>
77
/// Represents a rendered fragment.
88
/// </summary>
9-
public interface IRenderedFragment
9+
public interface IRenderedFragmentCore
1010
{
1111
/// <summary>
1212
/// Gets the id of the rendered component or fragment.
@@ -15,7 +15,7 @@ public interface IRenderedFragment
1515

1616
/// <summary>
1717
/// Gets an <see cref="IObservable{RenderEvent}"/> which will provide subscribers with <see cref="RenderEvent"/>s
18-
/// whenever the <see cref="IRenderedFragment"/> is rendered.
18+
/// whenever the <see cref="IRenderedFragmentCore"/> is rendered.
1919
/// </summary>
2020
IObservable<RenderEvent> RenderEvents { get; }
2121

src/bunit.core/ITestContext.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using Bunit.Rendering;
23
using Bunit.Rendering.RenderEvents;
34

45
namespace Bunit
@@ -8,6 +9,11 @@ namespace Bunit
89
/// </summary>
910
public partial interface ITestContext : IDisposable
1011
{
12+
/// <summary>
13+
/// Gets the renderer used by the test context.
14+
/// </summary>
15+
TestRenderer Renderer { get; }
16+
1117
/// <summary>
1218
/// Gets the service collection and service provider that is used when a
1319
/// component is rendered by the test context.

src/bunit.core/RazorTesting/Fixture.cs

Lines changed: 0 additions & 76 deletions
This file was deleted.
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Threading.Tasks;
4+
using Bunit.RazorTesting;
5+
using Bunit.Rendering;
6+
using Microsoft.AspNetCore.Components;
7+
using Microsoft.Extensions.DependencyInjection;
8+
9+
namespace Bunit
10+
{
11+
/// <summary>
12+
/// Represents a single fixture in a Razor based test. Used to
13+
/// define the <see cref="ComponentUnderTest"/> and any <see cref="Fragment"/>'s
14+
/// you might need during testing, and assert against them in the Test methods.
15+
/// </summary>
16+
public abstract class FixtureBase<TFixture> : RazorTest
17+
{
18+
/// <summary>
19+
/// Gets or sets the child content of the fragment.
20+
/// </summary>
21+
[Parameter] public RenderFragment ChildContent { get; set; } = default!;
22+
23+
/// <summary>
24+
/// Gets or sets the setup action to perform before the <see cref="Test"/> action,
25+
/// <see cref="TestAsync"/> action and <see cref="Tests"/> and <see cref="TestsAsync"/> actions are invoked.
26+
/// </summary>
27+
[Parameter] public Action<TFixture>? Setup { private get; set; }
28+
29+
/// <summary>
30+
/// Gets or sets the asynchronous setup action to perform before the <see cref="Test"/> action,
31+
/// <see cref="TestAsync"/> action and <see cref="Tests"/> and <see cref="TestsAsync"/> actions are invoked.
32+
/// </summary>
33+
[Parameter] public Func<TFixture, Task>? SetupAsync { private get; set; }
34+
35+
/// <summary>
36+
/// Gets or sets the first test action to invoke, after the <see cref="Setup"/> action has
37+
/// executed (if provided).
38+
///
39+
/// Use this to assert against the <see cref="ComponentUnderTest"/> and <see cref="Fragment"/>'s
40+
/// defined in the <see cref="FixtureBase"/>.
41+
/// </summary>
42+
[Parameter] public Action<TFixture>? Test { private get; set; }
43+
44+
/// <summary>
45+
/// Gets or sets the first test action to invoke, after the <see cref="SetupAsync"/> action has
46+
/// executed (if provided).
47+
///
48+
/// Use this to assert against the <see cref="ComponentUnderTest"/> and <see cref="Fragment"/>'s
49+
/// defined in the <see cref="FixtureBase"/>.
50+
/// </summary>
51+
[Parameter] public Func<TFixture, Task>? TestAsync { private get; set; }
52+
53+
/// <summary>
54+
/// Gets or sets the test actions to invoke, one at the time, in the order they are placed
55+
/// into the collection, after the <see cref="Setup"/> action and the <see cref="Test"/> action has
56+
/// executed (if provided).
57+
///
58+
/// Use this to assert against the <see cref="ComponentUnderTest"/> and <see cref="Fragment"/>'s
59+
/// defined in the <see cref="FixtureBase"/>.
60+
/// </summary>
61+
[Parameter] public IReadOnlyCollection<Action<TFixture>>? Tests { private get; set; }
62+
63+
/// <summary>
64+
/// Gets or sets the test actions to invoke, one at the time, in the order they are placed
65+
/// into the collection, after the <see cref="SetupAsync"/> action and the <see cref="TestAsync"/> action has
66+
/// executed (if provided).
67+
///
68+
/// Use this to assert against the <see cref="ComponentUnderTest"/> and <see cref="Fragment"/>'s
69+
/// defined in the <see cref="FixtureBase"/>.
70+
/// </summary>
71+
[Parameter] public IReadOnlyCollection<Func<TFixture, Task>>? TestsAsync { private get; set; }
72+
73+
/// <inheritdoc/>
74+
public override Task SetParametersAsync(ParameterView parameters)
75+
{
76+
if (parameters.TryGetValue<RenderFragment>(nameof(ChildContent), out RenderFragment childContent))
77+
ChildContent = childContent;
78+
else
79+
throw new InvalidOperationException($"No {nameof(ChildContent)} specified in the {GetType().Name} component.");
80+
81+
Setup = parameters.GetValueOrDefault<Action<TFixture>>(nameof(Setup));
82+
SetupAsync = parameters.GetValueOrDefault<Func<TFixture, Task>>(nameof(SetupAsync));
83+
Test = parameters.GetValueOrDefault<Action<TFixture>>(nameof(Test));
84+
TestAsync = parameters.GetValueOrDefault<Func<TFixture, Task>>(nameof(TestAsync));
85+
Tests = parameters.GetValueOrDefault<IReadOnlyCollection<Action<TFixture>>>(nameof(Tests));
86+
TestsAsync = parameters.GetValueOrDefault<IReadOnlyCollection<Func<TFixture, Task>>>(nameof(TestsAsync));
87+
88+
return base.SetParametersAsync(parameters);
89+
}
90+
91+
/// <inheritdoc/>
92+
protected virtual async Task Run(TFixture self)
93+
{
94+
if (Setup is { })
95+
TryRun(Setup, self);
96+
97+
if (SetupAsync is { })
98+
await TryRunAsync(SetupAsync, self).ConfigureAwait(false);
99+
100+
if (Test is { })
101+
TryRun(Test, self);
102+
103+
if (TestAsync is { })
104+
await TryRunAsync(TestAsync, self).ConfigureAwait(false);
105+
106+
foreach (var test in Tests ?? Array.Empty<Action<TFixture>>())
107+
TryRun(test, self);
108+
109+
foreach (var test in TestsAsync ?? Array.Empty<Func<TFixture, Task>>())
110+
await TryRunAsync(test, self).ConfigureAwait(false);
111+
}
112+
}
113+
}

src/bunit.core/RazorTesting/FixtureFailedException.cs

Lines changed: 0 additions & 21 deletions
This file was deleted.

src/bunit.core/RazorTesting/FragmentBase.cs

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,6 @@ namespace Bunit.RazorTesting
99
/// </summary>
1010
public abstract class FragmentBase : IComponent
1111
{
12-
/// <summary>
13-
/// A no-op test method.
14-
/// </summary>
15-
protected static void NoopTestMethod() { }
16-
17-
/// <summary>
18-
/// A no-op async test method
19-
/// </summary>
20-
protected static Task NoopTestMethodAsync() => Task.CompletedTask;
21-
2212
/// <summary>
2313
/// Gets or sets the child content of the fragment.
2414
/// </summary>

src/bunit.core/RazorTesting/RazorTest.cs

Lines changed: 69 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
11
using System;
2-
using System.Collections.Generic;
3-
using System.Linq;
4-
using System.Text;
52
using System.Threading.Tasks;
63
using Microsoft.AspNetCore.Components;
74

@@ -10,8 +7,13 @@ namespace Bunit.RazorTesting
107
/// <summary>
118
/// Represents a component used to define tests in Razor files.
129
/// </summary>
13-
public abstract class RazorTest : FragmentBase
10+
public abstract class RazorTest : TestContextBase, ITestContext, IComponent
1411
{
12+
/// <summary>
13+
/// Gets whether the tests is running or not.
14+
/// </summary>
15+
public bool IsRunning { get; private set; }
16+
1517
/// <summary>
1618
/// A description or name for the test that will be displayed if the test fails.
1719
/// </summary>
@@ -25,10 +27,72 @@ public abstract class RazorTest : FragmentBase
2527
/// <summary>
2628
/// Run the test logic of the <see cref="RazorTest"/>.
2729
/// </summary>
30+
/// <exception cref="InvalidOperationException">Thrown when called and <see cref="IsRunning"/> is true.</exception>
2831
/// <returns></returns>
29-
public abstract Task RunTest();
32+
public async Task RunTest()
33+
{
34+
if (IsRunning)
35+
throw new InvalidOperationException("The fixture test is already running.");
36+
37+
IsRunning = true;
38+
39+
try
40+
{
41+
await Run().ConfigureAwait(false);
42+
}
43+
finally
44+
{
45+
IsRunning = false;
46+
}
47+
}
3048

3149
/// <inheritdoc/>
3250
public override string ToString() => $"{Description ?? "[no description]"}";
51+
52+
/// <inheritdoc/>
53+
public virtual Task SetParametersAsync(ParameterView parameters)
54+
{
55+
Skip = parameters.GetValueOrDefault<string>(nameof(Skip));
56+
Description = parameters.GetValueOrDefault<string>(nameof(Description));
57+
return Task.CompletedTask;
58+
}
59+
60+
/// <inheritdoc/>
61+
void IComponent.Attach(RenderHandle renderHandle) { }
62+
63+
/// <summary>
64+
/// Implements the logic for running the test.
65+
/// </summary>
66+
protected abstract Task Run();
67+
68+
/// <summary>
69+
/// Try to run the <see cref="Action{T}"/>.
70+
/// </summary>
71+
protected void TryRun<T>(Action<T> action, T input)
72+
{
73+
try
74+
{
75+
action(input);
76+
}
77+
catch (Exception ex)
78+
{
79+
throw new RazorTestFailedException(Description ?? $"{action.Method.Name} failed:", ex);
80+
}
81+
}
82+
83+
/// <summary>
84+
/// Try to run the <see cref="Func{T, Task}"/>.
85+
/// </summary>
86+
protected async Task TryRunAsync<T>(Func<T, Task> action, T input)
87+
{
88+
try
89+
{
90+
await action(input).ConfigureAwait(false);
91+
}
92+
catch (Exception ex)
93+
{
94+
throw new RazorTestFailedException(Description ?? $"{action.Method.Name} failed:", ex);
95+
}
96+
}
3397
}
3498
}

0 commit comments

Comments
 (0)