Skip to content

Commit 047bb18

Browse files
committed
Improved error handling in RazorTestContext Get-methods.
1 parent 600285a commit 047bb18

File tree

6 files changed

+145
-43
lines changed

6 files changed

+145
-43
lines changed

src/IRazorTestContext.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ namespace Egil.RazorComponents.Testing
99
public interface IRazorTestContext : ITestContext
1010
{
1111
/// <summary>
12-
/// Gets (and renders) the HTML/component defined in the &lt;Fixture&gt;&lt;ComponentUnderTest&gt;...&lt;ComponentUnderTest/&gt;&lt;Fixture/&gt; element.
12+
/// Gets (and renders) the markup/component defined in the &lt;Fixture&gt;&lt;ComponentUnderTest&gt;...&lt;ComponentUnderTest/&gt;&lt;Fixture/&gt; element.
1313
///
1414
/// The HTML/component is only rendered the first this method is called.
1515
/// </summary>
@@ -27,7 +27,7 @@ public interface IRazorTestContext : ITestContext
2727
IRenderedComponent<TComponent> GetComponentUnderTest<TComponent>() where TComponent : class, IComponent;
2828

2929
/// <summary>
30-
/// Gets (and renders) the HTML/component defined in the
30+
/// Gets (and renders) the markup/component defined in the
3131
/// &lt;Fixture&gt;&lt;Fragment id="<paramref name="id"/>" &gt;...&lt;Fragment/&gt;&lt;Fixture/&gt; element.
3232
///
3333
/// If <paramref name="id"/> is null/not provided, the component defined in the first &lt;Fragment/&gt; in

src/RazorTestContext.cs

Lines changed: 46 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -25,70 +25,51 @@ public RazorTestContext(IReadOnlyList<FragmentBase> testData)
2525
}
2626

2727
/// <inheritdoc/>
28-
public IRenderedFragment GetComponentUnderTest()
29-
{
30-
return GetOrRenderFragment(nameof(GetComponentUnderTest), SelectComponentUnderTest, Factory);
31-
32-
static IRenderedFragment Factory(RazorTestContext context, RenderFragment fragment)
33-
=> new RenderedFragment(context, fragment);
34-
}
28+
public IRenderedFragment GetComponentUnderTest() => GetOrRenderFragment(nameof(GetComponentUnderTest), SelectComponentUnderTest, Factory);
3529

3630
/// <inheritdoc/>
3731
public IRenderedComponent<TComponent> GetComponentUnderTest<TComponent>() where TComponent : class, IComponent
3832
{
39-
return GetOrRenderFragment(nameof(GetComponentUnderTest), SelectComponentUnderTest, Factory);
40-
41-
static IRenderedComponent<TComponent> Factory(RazorTestContext context, RenderFragment fragment)
42-
=> new RenderedComponent<TComponent>(context, fragment);
33+
var result = GetOrRenderFragment(nameof(GetComponentUnderTest), SelectComponentUnderTest, Factory<TComponent>);
34+
return TryCastTo<TComponent>(result);
4335
}
4436

4537
/// <inheritdoc/>
46-
public IRenderedComponent<TComponent> GetFragment<TComponent>(string? id = null) where TComponent : class, IComponent
38+
public IRenderedFragment GetFragment(string? id = null)
4739
{
48-
var key = id ?? nameof(Fragment);
49-
50-
return id is null
51-
? GetOrRenderFragment(key, SelectFirstFragment, Factory)
52-
: GetOrRenderFragment(key, SelectFragmentById, Factory);
53-
54-
static IRenderedComponent<TComponent> Factory(RazorTestContext context, RenderFragment fragment)
55-
=> new RenderedComponent<TComponent>(context, fragment);
40+
var key = id ?? SelectFirstFragment().Id;
41+
var result = GetOrRenderFragment(key, SelectFragmentById, Factory);
42+
return result;
5643
}
5744

5845
/// <inheritdoc/>
59-
public IRenderedFragment GetFragment(string? id = null)
46+
public IRenderedComponent<TComponent> GetFragment<TComponent>(string? id = null) where TComponent : class, IComponent
6047
{
61-
var key = id ?? nameof(Fragment);
62-
63-
return id is null
64-
? GetOrRenderFragment(key, SelectFirstFragment, Factory)
65-
: GetOrRenderFragment(key, SelectFragmentById, Factory);
66-
67-
static IRenderedFragment Factory(RazorTestContext context, RenderFragment fragment)
68-
=> new RenderedFragment(context, fragment);
48+
var key = id ?? SelectFirstFragment().Id;
49+
var result = GetOrRenderFragment(key, SelectFragmentById, Factory<TComponent>);
50+
return TryCastTo<TComponent>(result);
6951
}
7052

7153
/// <summary>
7254
/// Gets or renders the fragment specified in the id.
7355
/// For internal use mainly.
7456
/// </summary>
75-
protected TRenderedFragment GetOrRenderFragment<TRenderedFragment>(string id, Func<string, FragmentBase> fragmentSelector, Func<RazorTestContext, RenderFragment, TRenderedFragment> renderedFragmentFactory)
76-
where TRenderedFragment : IRenderedFragment
57+
private IRenderedFragment GetOrRenderFragment(string id, Func<string, FragmentBase> fragmentSelector, Func<RazorTestContext, RenderFragment, IRenderedFragment> renderedFragmentFactory)
7758
{
7859
if (_renderedFragments.TryGetValue(id, out var renderedFragment))
7960
{
80-
return (TRenderedFragment)renderedFragment;
61+
return renderedFragment;
8162
}
8263
else
8364
{
84-
var fragment = fragmentSelector(id);//
65+
var fragment = fragmentSelector(id);
8566
var result = renderedFragmentFactory(this, fragment.ChildContent);
8667
_renderedFragments.Add(id, result);
8768
return result;
8869
}
8970
}
9071

91-
private Fragment SelectFirstFragment(string _)
72+
private Fragment SelectFirstFragment()
9273
{
9374
return _testData.OfType<Fragment>().First();
9475
}
@@ -102,6 +83,37 @@ private ComponentUnderTest SelectComponentUnderTest(string _)
10283
{
10384
return _testData.OfType<ComponentUnderTest>().Single();
10485
}
86+
87+
private IRenderedComponent<TComponent> Factory<TComponent>(RazorTestContext context, RenderFragment fragment) where TComponent : class, IComponent
88+
=> new RenderedComponent<TComponent>(context, fragment);
89+
90+
private IRenderedFragment Factory(RazorTestContext context, RenderFragment fragment)
91+
=> new RenderedFragment(context, fragment);
92+
93+
private IRenderedComponent<TComponent> TryCastTo<TComponent>(IRenderedFragment target, [System.Runtime.CompilerServices.CallerMemberName] string sourceMethod = "") where TComponent : class, IComponent
94+
{
95+
if (target is IRenderedComponent<TComponent> result)
96+
{
97+
return result;
98+
}
99+
100+
if (target is IRenderedComponent<IComponent> other)
101+
{
102+
throw new InvalidOperationException($"The generic version of {sourceMethod} has previously returned an object of type IRenderedComponent<{other.Instance.GetType().Name}>. " +
103+
$"That cannot be cast to an object of type IRenderedComponent<{typeof(TComponent).Name}>.");
104+
}
105+
106+
if (target is IRenderedFragment)
107+
{
108+
throw new InvalidOperationException($"It is not possible to call the generic version of {sourceMethod} after " +
109+
$"the non-generic version has been called on the same test context. Change all calls to the same generic version and try again.");
110+
}
111+
else
112+
{
113+
throw new Exception($"This line should never have been reached. An unknown type was placed inside the {nameof(_renderedFragments)}.");
114+
}
115+
}
116+
105117
}
106118

107119
}

src/Rendering/IRenderedComponent.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ namespace Egil.RazorComponents.Testing
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<TComponent> : IRenderedFragment where TComponent : class, IComponent
10+
public interface IRenderedComponent<out TComponent> : IRenderedFragment where TComponent : class, IComponent
1111
{
1212
/// <summary>
1313
/// Gets the component under test

src/Rendering/RenderedFragment.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public class RenderedFragment : IRenderedFragment
2222
/// Gets and sets the id of the fragment being rendered.
2323
/// </summary>
2424
protected int ComponentId { get; set; }
25-
25+
2626
/// <summary>
2727
/// Gets the container that handles the (re)rendering of the fragment.
2828
/// </summary>

tests/FixtureMethodsShouldBeCalledInExpectedOrder.razor

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
@inherits TestComponentBase
22
@using Shouldly
3-
@*
4-
TODO:
5-
- ComponentUnderTest is returned by context.GetComponentUnderTest<T>
6-
- Multiple calls to context.GetComponentUnderTest<T> returns same instance
7-
*@
3+
84
<Fixture Setup="Setup" Test="Test1" Tests=@(new Test[] { Test2, Test3 })>
95
<ComponentUnderTest><div/></ComponentUnderTest>
106
</Fixture>
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
@inherits TestComponentBase
2+
@using Xunit
3+
4+
<Fixture Test="CallingGetMultipleTimesReturnsSameInstance">
5+
<ComponentUnderTest>
6+
<Wrapper>CUT</Wrapper>
7+
</ComponentUnderTest>
8+
<Fragment Id="first">first</Fragment>
9+
<Fragment Id="second">
10+
<Wrapper>second</Wrapper>
11+
</Fragment>
12+
</Fixture>
13+
@code{
14+
void CallingGetMultipleTimesReturnsSameInstance(IRazorTestContext context)
15+
{
16+
var cut1 = context.GetComponentUnderTest<Wrapper>();
17+
var cut2 = context.GetComponentUnderTest<Wrapper>();
18+
19+
Assert.True(ReferenceEquals(cut1, cut2), "Getting CUT multiple times should return the same instance");
20+
Assert.Equal("CUT", cut1.GetMarkup());
21+
22+
var firstFragmentNoId1 = context.GetFragment();
23+
var firstFragmentId1 = context.GetFragment("first");
24+
var firstFragmentNoId2 = context.GetFragment();
25+
var firstFragmentId2 = context.GetFragment("first");
26+
Assert.True(ReferenceEquals(firstFragmentNoId1, firstFragmentId1), "Getting first fragment with and without id should return the same instance");
27+
Assert.True(ReferenceEquals(firstFragmentNoId1, firstFragmentNoId2), "Getting first fragment multiple times should return the same instance");
28+
Assert.True(ReferenceEquals(firstFragmentId1, firstFragmentId2), "Getting first fragment multiple times should return the same instance");
29+
Assert.Equal("first", firstFragmentNoId1.GetMarkup());
30+
31+
var secondFragmentId1 = context.GetFragment<Wrapper>("second");
32+
var secondFragmentId2 = context.GetFragment<Wrapper>("second");
33+
34+
Assert.True(ReferenceEquals(secondFragmentId1, secondFragmentId2), "Getting fragment multiple times should return the same instance");
35+
Assert.Equal("second", secondFragmentId2.GetMarkup());
36+
}
37+
}
38+
39+
<Fixture Test="CallingGenericGetAfterNonGenericGetThrows">
40+
<ComponentUnderTest>
41+
<Wrapper>CUT</Wrapper>
42+
</ComponentUnderTest>
43+
<Fragment>
44+
<Wrapper>second</Wrapper>
45+
</Fragment>
46+
</Fixture>
47+
@code{
48+
void CallingGenericGetAfterNonGenericGetThrows(IRazorTestContext context)
49+
{
50+
context.GetComponentUnderTest();
51+
52+
// It should not be possible to call the generic GetComponentUnderTest after the non-generic has been called
53+
Assert.Throws<InvalidOperationException>(() => context.GetComponentUnderTest<Wrapper>());
54+
55+
context.GetFragment();
56+
57+
// It should not be possible to call the generic GetFragment after the non-generic has been called
58+
Assert.Throws<InvalidOperationException>(() => context.GetFragment<Wrapper>());
59+
}
60+
}
61+
62+
<Fixture Test="CallingGetCutOrGetFragmentWithWrongGenericTypeThrows">
63+
<ComponentUnderTest>
64+
<Wrapper></Wrapper>
65+
</ComponentUnderTest>
66+
<Fragment>
67+
<Wrapper></Wrapper>
68+
</Fragment>
69+
</Fixture>
70+
@code{
71+
void CallingGetCutOrGetFragmentWithWrongGenericTypeThrows(IRazorTestContext context)
72+
{
73+
Assert.Throws<InvalidOperationException>(() => context.GetComponentUnderTest<Simple1>());
74+
Assert.Throws<InvalidOperationException>(() => context.GetFragment<Simple1>());
75+
}
76+
}
77+
78+
<Fixture Test="CallingGetCutOrGetFragmentWithIncompatibleGenericTypeThrows">
79+
<ComponentUnderTest>
80+
<Wrapper></Wrapper>
81+
</ComponentUnderTest>
82+
<Fragment>
83+
<Wrapper></Wrapper>
84+
</Fragment>
85+
</Fixture>
86+
@code{
87+
void CallingGetCutOrGetFragmentWithIncompatibleGenericTypeThrows(IRazorTestContext context)
88+
{
89+
context.GetComponentUnderTest<Wrapper>();
90+
context.GetFragment<Wrapper>();
91+
Assert.Throws<InvalidOperationException>(() => context.GetComponentUnderTest<Simple1>());
92+
Assert.Throws<InvalidOperationException>(() => context.GetFragment<Simple1>());
93+
}
94+
}

0 commit comments

Comments
 (0)