Skip to content

Commit 2249acc

Browse files
committed
Added ability to render a fragment to test context, and pass it as the expected value to markup matches methods
1 parent a3fc598 commit 2249acc

13 files changed

Lines changed: 375 additions & 64 deletions

src/bunit.core/RazorTesting/FragmentContainer.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ namespace Bunit.RazorTesting
99
/// when a fragment is rendered inside a test contexts render tree.
1010
/// It is primarily used to be able to find the starting point to return.
1111
/// </summary>
12-
internal sealed class FragmentContainer : ComponentBase
12+
public sealed class FragmentContainer : ComponentBase
1313
{
1414
/// <summary>
1515
/// The content to wrap.
@@ -25,7 +25,7 @@ protected override void BuildRenderTree(RenderTreeBuilder builder)
2525
/// <summary>
2626
/// Wraps the <paramref name="wrappingTarget"/> in a <see cref="FragmentContainer"/>.
2727
/// </summary>
28-
internal static RenderFragment Wrap(RenderFragment wrappingTarget)
28+
public static RenderFragment Wrap(RenderFragment wrappingTarget)
2929
{
3030
return builder =>
3131
{

src/bunit.core/TestContextBase.cs

Lines changed: 0 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
using System;
2-
using Bunit.RazorTesting;
32
using Bunit.Rendering;
4-
using Microsoft.AspNetCore.Components;
53
using Microsoft.Extensions.DependencyInjection;
64

75
namespace Bunit
@@ -53,46 +51,6 @@ protected TestContextBase()
5351
Services = new TestServiceProvider();
5452
}
5553

56-
/// <summary>
57-
/// Renders a component, declared in the <paramref name="renderFragment"/>, inside the <see cref="RenderTree"/>.
58-
/// </summary>
59-
/// <typeparam name="TComponent">The type of component to render.</typeparam>
60-
/// <param name="renderFragment">The <see cref="RenderFragmentBase"/> that contains a declaration of the component.</param>
61-
/// <returns>A <see cref="IRenderedComponentBase{TComponent}"/>.</returns>
62-
protected IRenderedComponentBase<TComponent> RenderComponentBase<TComponent>(RenderFragment renderFragment) where TComponent : IComponent
63-
{
64-
// Wrap TComponent in any layout components added to the test context.
65-
// If one of the layout components is the same type as TComponent,
66-
// make sure to return the rendered component, not the layout component.
67-
var resultBase = Renderer.RenderFragment(RenderTree.Wrap(renderFragment));
68-
69-
// This ensures that the correct component is returned, in case an added layout component
70-
// is of type TComponent.
71-
var renderTreeTComponentCount = RenderTree.GetCountOf<TComponent>();
72-
var result = renderTreeTComponentCount > 0
73-
? Renderer.FindComponents<TComponent>(resultBase)[renderTreeTComponentCount]
74-
: Renderer.FindComponent<TComponent>(resultBase);
75-
76-
return result;
77-
}
78-
79-
/// <summary>
80-
/// Renders a fragment, declared in the <paramref name="renderFragment"/>, inside the <see cref="RenderTree"/>.
81-
/// </summary>
82-
/// <param name="renderFragment">The <see cref="RenderFragmentBase"/> to render.</param>
83-
/// <returns>A <see cref="IRenderedFragmentBase"/>.</returns>
84-
protected IRenderedFragmentBase RenderFragmentBase(RenderFragment renderFragment)
85-
{
86-
// Wrap fragment in a FragmentContainer so the start of the test supplied
87-
// razor fragment can be found after, and then wrap in any layout components
88-
// added to the test context.
89-
var wrappedInFragmentContainer = FragmentContainer.Wrap(renderFragment);
90-
var wrappedInRenderTree = RenderTree.Wrap(wrappedInFragmentContainer);
91-
var resultBase = Renderer.RenderFragment(wrappedInRenderTree);
92-
93-
return Renderer.FindComponent<FragmentContainer>(resultBase);
94-
}
95-
9654
/// <inheritdoc/>
9755
public void Dispose()
9856
{

src/bunit.web/Asserting/MarkupMatchesAssertExtensions.cs

Lines changed: 101 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using Bunit.Asserting;
44
using Bunit.Diffing;
55
using Bunit.Rendering;
6+
using Microsoft.AspNetCore.Components;
67
using Microsoft.Extensions.DependencyInjection;
78

89
namespace Bunit
@@ -23,6 +24,11 @@ public static class MarkupMatchesAssertExtensions
2324
[AssertionMethod]
2425
public static void MarkupMatches(this string actual, string expected, string? userMessage = null)
2526
{
27+
if (actual is null)
28+
throw new ArgumentNullException(nameof(actual));
29+
if (expected is null)
30+
throw new ArgumentNullException(nameof(expected));
31+
2632
using var parser = new BunitHtmlParser();
2733
var actualNodes = parser.Parse(actual);
2834
var expectedNodes = parser.Parse(expected);
@@ -31,15 +37,17 @@ public static void MarkupMatches(this string actual, string expected, string? us
3137

3238
/// <summary>
3339
/// Verifies that the rendered markup from the <paramref name="actual"/> markup fragment matches
34-
/// the <paramref name="expected"/> <see cref="IRenderedFragmentBase"/>, using the <see cref="HtmlComparer"/> type.
40+
/// the <paramref name="expected"/> <see cref="IRenderedFragment"/>, using the <see cref="HtmlComparer"/> type.
3541
/// </summary>
3642
/// <exception cref="HtmlEqualException">Thrown when the <paramref name="actual"/> markup does not match the <paramref name="expected"/> markup.</exception>
3743
/// <param name="actual">The markup fragment to verify.</param>
38-
/// <param name="expected">The expected <see cref="IRenderedFragmentBase"/>.</param>
44+
/// <param name="expected">The expected <see cref="IRenderedFragment"/>.</param>
3945
/// <param name="userMessage">A custom user message to display in case the verification fails.</param>
4046
[AssertionMethod]
4147
public static void MarkupMatches(this string actual, IRenderedFragment expected, string? userMessage = null)
4248
{
49+
if (actual is null)
50+
throw new ArgumentNullException(nameof(actual));
4351
if (expected is null)
4452
throw new ArgumentNullException(nameof(expected));
4553

@@ -58,6 +66,8 @@ public static void MarkupMatches(this string actual, IRenderedFragment expected,
5866
[AssertionMethod]
5967
public static void MarkupMatches(this string actual, INodeList expected, string? userMessage = null)
6068
{
69+
if (actual is null)
70+
throw new ArgumentNullException(nameof(actual));
6171
if (expected is null)
6272
throw new ArgumentNullException(nameof(expected));
6373

@@ -76,6 +86,8 @@ public static void MarkupMatches(this string actual, INodeList expected, string?
7686
[AssertionMethod]
7787
public static void MarkupMatches(this string actual, INode expected, string? userMessage = null)
7888
{
89+
if (actual is null)
90+
throw new ArgumentNullException(nameof(actual));
7991
if (expected is null)
8092
throw new ArgumentNullException(nameof(expected));
8193

@@ -84,7 +96,7 @@ public static void MarkupMatches(this string actual, INode expected, string? use
8496
}
8597

8698
/// <summary>
87-
/// Verifies that the rendered markup from the <paramref name="actual"/> <see cref="IRenderedFragmentBase"/> matches
99+
/// Verifies that the rendered markup from the <paramref name="actual"/> <see cref="IRenderedFragment"/> matches
88100
/// the <paramref name="expected"/> markup, using the <see cref="HtmlComparer"/> type.
89101
/// </summary>
90102
/// <exception cref="HtmlEqualException">Thrown when the <paramref name="actual"/> markup does not match the <paramref name="expected"/> markup.</exception>
@@ -104,8 +116,8 @@ public static void MarkupMatches(this IRenderedFragment actual, string expected,
104116
}
105117

106118
/// <summary>
107-
/// Verifies that the rendered markup from the <paramref name="actual"/> <see cref="IRenderedFragmentBase"/> matches
108-
/// the rendered markup from the <paramref name="expected"/> <see cref="IRenderedFragmentBase"/>, using the <see cref="HtmlComparer"/> type.
119+
/// Verifies that the rendered markup from the <paramref name="actual"/> <see cref="IRenderedFragment"/> matches
120+
/// the rendered markup from the <paramref name="expected"/> <see cref="IRenderedFragment"/>, using the <see cref="HtmlComparer"/> type.
109121
/// </summary>
110122
/// <exception cref="HtmlEqualException">Thrown when the <paramref name="actual"/> markup does not match the <paramref name="expected"/> markup.</exception>
111123
/// <param name="actual">The rendered fragment to verify.</param>
@@ -124,7 +136,7 @@ public static void MarkupMatches(this IRenderedFragment actual, IRenderedFragmen
124136

125137
/// <summary>
126138
/// Verifies that the <paramref name="actual"/> <see cref="INodeList"/> matches
127-
/// the rendered markup from the <paramref name="expected"/> <see cref="IRenderedFragmentBase"/>, using the <see cref="HtmlComparer"/>
139+
/// the rendered markup from the <paramref name="expected"/> <see cref="IRenderedFragment"/>, using the <see cref="HtmlComparer"/>
128140
/// type.
129141
/// </summary>
130142
/// <exception cref="HtmlEqualException">Thrown when the <paramref name="actual"/> markup does not match the <paramref name="expected"/> markup.</exception>
@@ -144,7 +156,7 @@ public static void MarkupMatches(this INodeList actual, IRenderedFragment expect
144156

145157
/// <summary>
146158
/// Verifies that the <paramref name="actual"/> <see cref="INode"/> matches
147-
/// the rendered markup from the <paramref name="expected"/> <see cref="IRenderedFragmentBase"/>, using the <see cref="HtmlComparer"/>
159+
/// the rendered markup from the <paramref name="expected"/> <see cref="IRenderedFragment"/>, using the <see cref="HtmlComparer"/>
148160
/// type.
149161
/// </summary>
150162
/// <exception cref="HtmlEqualException">Thrown when the <paramref name="actual"/> markup does not match the <paramref name="expected"/> markup.</exception>
@@ -176,6 +188,8 @@ public static void MarkupMatches(this INode actual, string expected, string? use
176188
{
177189
if (actual is null)
178190
throw new ArgumentNullException(nameof(actual));
191+
if (expected is null)
192+
throw new ArgumentNullException(nameof(expected));
179193

180194
var expectedNodes = expected.ToNodeList(actual.GetHtmlParser());
181195
actual.MarkupMatches(expectedNodes, userMessage);
@@ -195,6 +209,8 @@ public static void MarkupMatches(this INodeList actual, string expected, string?
195209
{
196210
if (actual is null)
197211
throw new ArgumentNullException(nameof(actual));
212+
if (expected is null)
213+
throw new ArgumentNullException(nameof(expected));
198214

199215
var expectedNodes = expected.ToNodeList(actual.GetHtmlParser());
200216
actual.MarkupMatches(expectedNodes, userMessage);
@@ -212,6 +228,11 @@ public static void MarkupMatches(this INodeList actual, string expected, string?
212228
[AssertionMethod]
213229
public static void MarkupMatches(this INodeList actual, INodeList expected, string? userMessage = null)
214230
{
231+
if (actual is null)
232+
throw new ArgumentNullException(nameof(actual));
233+
if (expected is null)
234+
throw new ArgumentNullException(nameof(expected));
235+
215236
var diffs = actual.CompareTo(expected);
216237

217238
if (diffs.Count != 0)
@@ -230,6 +251,11 @@ public static void MarkupMatches(this INodeList actual, INodeList expected, stri
230251
[AssertionMethod]
231252
public static void MarkupMatches(this INodeList actual, INode expected, string? userMessage = null)
232253
{
254+
if (actual is null)
255+
throw new ArgumentNullException(nameof(actual));
256+
if (expected is null)
257+
throw new ArgumentNullException(nameof(expected));
258+
233259
var diffs = actual.CompareTo(expected);
234260

235261
if (diffs.Count != 0)
@@ -248,12 +274,80 @@ public static void MarkupMatches(this INodeList actual, INode expected, string?
248274
[AssertionMethod]
249275
public static void MarkupMatches(this INode actual, INodeList expected, string? userMessage = null)
250276
{
277+
if (actual is null)
278+
throw new ArgumentNullException(nameof(actual));
279+
if (expected is null)
280+
throw new ArgumentNullException(nameof(expected));
281+
251282
var diffs = actual.CompareTo(expected);
252283

253284
if (diffs.Count != 0)
254285
throw new HtmlEqualException(diffs, expected, actual, userMessage);
255286
}
256287

288+
/// <summary>
289+
/// Verifies that the rendered markup from the <paramref name="actual"/> <see cref="IRenderedFragment"/> matches
290+
/// the rendered markup from the <paramref name="expected"/> <see cref="RenderFragment"/>, using the <see cref="HtmlComparer"/> type.
291+
/// </summary>
292+
/// <exception cref="HtmlEqualException">Thrown when the <paramref name="actual"/> markup does not match the <paramref name="expected"/> markup.</exception>
293+
/// <param name="actual">The rendered fragment to verify.</param>
294+
/// <param name="expected">The render fragment whose output to compare against.</param>
295+
/// <param name="userMessage">A custom user message to display in case the verification fails.</param>
296+
[AssertionMethod]
297+
public static void MarkupMatches(this IRenderedFragment actual, RenderFragment expected, string? userMessage = null)
298+
{
299+
if (actual is null)
300+
throw new ArgumentNullException(nameof(actual));
301+
if (expected is null)
302+
throw new ArgumentNullException(nameof(expected));
303+
304+
var testContext = actual.Services.GetRequiredService<TestContext>();
305+
var renderedFragment = testContext.Render(expected);
306+
MarkupMatches(actual, renderedFragment, userMessage);
307+
}
308+
309+
/// <summary>
310+
/// Verifies that the markup from the <paramref name="actual"/> matches
311+
/// the rendered markup from the <paramref name="expected"/> <see cref="RenderFragment"/>, using the <see cref="HtmlComparer"/> type.
312+
/// </summary>
313+
/// <exception cref="HtmlEqualException">Thrown when the <paramref name="actual"/> markup does not match the <paramref name="expected"/> markup.</exception>
314+
/// <param name="actual">The markup to verify.</param>
315+
/// <param name="expected">The render fragment whose output to compare against.</param>
316+
/// <param name="userMessage">A custom user message to display in case the verification fails.</param>
317+
[AssertionMethod]
318+
public static void MarkupMatches(this INode actual, RenderFragment expected, string? userMessage = null)
319+
{
320+
if (actual is null)
321+
throw new ArgumentNullException(nameof(actual));
322+
if (expected is null)
323+
throw new ArgumentNullException(nameof(expected));
324+
325+
var testContext = actual.GetTestContext() ?? new TestContext();
326+
var renderedFragment = testContext.Render(expected);
327+
MarkupMatches(actual, renderedFragment, userMessage);
328+
}
329+
330+
/// <summary>
331+
/// Verifies that the markup from the <paramref name="actual"/> matches
332+
/// the rendered markup from the <paramref name="expected"/> <see cref="RenderFragment"/>, using the <see cref="HtmlComparer"/> type.
333+
/// </summary>
334+
/// <exception cref="HtmlEqualException">Thrown when the <paramref name="actual"/> markup does not match the <paramref name="expected"/> markup.</exception>
335+
/// <param name="actual">The markup to verify.</param>
336+
/// <param name="expected">The render fragment whose output to compare against.</param>
337+
/// <param name="userMessage">A custom user message to display in case the verification fails.</param>
338+
[AssertionMethod]
339+
public static void MarkupMatches(this INodeList actual, RenderFragment expected, string? userMessage = null)
340+
{
341+
if (actual is null)
342+
throw new ArgumentNullException(nameof(actual));
343+
if (expected is null)
344+
throw new ArgumentNullException(nameof(expected));
345+
346+
var testContext = actual.GetTestContext() ?? new TestContext();
347+
var renderedFragment = testContext.Render(expected);
348+
MarkupMatches(actual, renderedFragment, userMessage);
349+
}
350+
257351
private static INodeList ToNodeList(this string markup, BunitHtmlParser? htmlParser)
258352
{
259353
if (htmlParser is null)

src/bunit.web/Extensions/Internal/AngleSharpExtensions.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,28 @@ public static IEnumerable<INode> AsEnumerable(this INode node)
6565
return nodes?.Length > 0 ? nodes[0].GetHtmlComparer() : null;
6666
}
6767

68+
/// <summary>
69+
/// Gets the <see cref="TestContext"/> stored in the <paramref name="node"/>s
70+
/// owning context, if one is available.
71+
/// </summary>
72+
/// <param name="node"></param>
73+
/// <returns>The <see cref="TestContext"/> or null if not found.</returns>
74+
public static TestContext? GetTestContext(this INode? node)
75+
{
76+
return node?.Owner.Context.GetService<TestContext>();
77+
}
78+
79+
/// <summary>
80+
/// Gets the <see cref="TestContext"/> stored in the <paramref name="nodes"/>s
81+
/// owning context, if one is available.
82+
/// </summary>
83+
/// <param name="nodes"></param>
84+
/// <returns>The <see cref="TestContext"/> or null if not found.</returns>
85+
public static TestContext? GetTestContext(this INodeList nodes)
86+
{
87+
return nodes?.Length > 0 ? nodes[0].GetTestContext() : null;
88+
}
89+
6890
/// <summary>
6991
/// Gets the parents of the <paramref name="element"/>, starting with
7092
/// the <paramref name="element"/> itself.
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
using Bunit.RazorTesting;
2+
using Microsoft.AspNetCore.Components;
3+
4+
namespace Bunit.Extensions
5+
{
6+
internal static class TestContextExtensions
7+
{
8+
/// <summary>
9+
/// Renders a component, declared in the <paramref name="renderFragment"/>, inside the <see cref="TestContextBase.RenderTree"/>.
10+
/// </summary>
11+
/// <typeparam name="TComponent">The type of component to render.</typeparam>
12+
/// <param name="testContext">Test context to use to render with.</param>
13+
/// <param name="renderFragment">The <see cref="RenderInsideRenderTree"/> that contains a declaration of the component.</param>
14+
/// <returns>A <see cref="IRenderedComponentBase{TComponent}"/>.</returns>
15+
public static IRenderedComponent<TComponent> RenderInsideRenderTree<TComponent>(this TestContextBase testContext, RenderFragment renderFragment) where TComponent : IComponent
16+
{
17+
// Wrap TComponent in any layout components added to the test context.
18+
// If one of the layout components is the same type as TComponent,
19+
// make sure to return the rendered component, not the layout component.
20+
var resultBase = testContext.Renderer.RenderFragment(testContext.RenderTree.Wrap(renderFragment));
21+
22+
// This ensures that the correct component is returned, in case an added layout component
23+
// is of type TComponent.
24+
var renderTreeTComponentCount = testContext.RenderTree.GetCountOf<TComponent>();
25+
var result = renderTreeTComponentCount > 0
26+
? testContext.Renderer.FindComponents<TComponent>(resultBase)[renderTreeTComponentCount]
27+
: testContext.Renderer.FindComponent<TComponent>(resultBase);
28+
29+
return (IRenderedComponent<TComponent>)result;
30+
}
31+
32+
/// <summary>
33+
/// Renders a fragment, declared in the <paramref name="renderFragment"/>, inside the <see cref="TestContextBase.RenderTree"/>.
34+
/// </summary>
35+
/// <param name="testContext">Test context to use to render with.</param>
36+
/// <param name="renderFragment">The <see cref="RenderInsideRenderTree"/> to render.</param>
37+
/// <returns>A <see cref="IRenderedFragmentBase"/>.</returns>
38+
public static IRenderedFragment RenderInsideRenderTree(this TestContextBase testContext, RenderFragment renderFragment)
39+
{
40+
// Wrap fragment in a FragmentContainer so the start of the test supplied
41+
// razor fragment can be found after, and then wrap in any layout components
42+
// added to the test context.
43+
var wrappedInFragmentContainer = FragmentContainer.Wrap(renderFragment);
44+
var wrappedInRenderTree = testContext.RenderTree.Wrap(wrappedInFragmentContainer);
45+
var resultBase = testContext.Renderer.RenderFragment(wrappedInRenderTree);
46+
47+
return (IRenderedFragment)testContext.Renderer.FindComponent<FragmentContainer>(resultBase);
48+
}
49+
50+
}
51+
}

src/bunit.web/IRenderedFragment.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,5 @@ public interface IRenderedFragment : IRenderedFragmentBase
4848
/// the snapshot and the rendered markup at that time.
4949
/// </summary>
5050
void SaveSnapshot();
51-
52-
5351
}
5452
}

src/bunit.web/RazorTesting/Fixture.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,12 +142,12 @@ private ComponentUnderTest SelectComponentUnderTest(string _)
142142

143143
private IRenderedComponent<TComponent> Factory<TComponent>(RenderFragment fragment) where TComponent : IComponent
144144
{
145-
return (IRenderedComponent<TComponent>)RenderComponentBase<TComponent>(fragment);
145+
return this.RenderInsideRenderTree<TComponent>(fragment);
146146
}
147147

148148
private IRenderedFragment Factory(RenderFragment fragment)
149149
{
150-
return (IRenderedFragment)RenderFragmentBase(fragment);
150+
return this.RenderInsideRenderTree(fragment);
151151
}
152152

153153
private static IRenderedComponent<TComponent> TryCastTo<TComponent>(IRenderedFragment target, [System.Runtime.CompilerServices.CallerMemberName] string sourceMethod = "") where TComponent : IComponent

0 commit comments

Comments
 (0)