Skip to content

Commit 30227f0

Browse files
committed
Refactored MockJSRuntime into Bunit JSInterop
1 parent 83b1a50 commit 30227f0

24 files changed

Lines changed: 373 additions & 448 deletions

src/bunit.web/Extensions/TestServiceProviderExtensions.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ public static IServiceCollection AddDefaultTestContextServices(this IServiceColl
2727
services.AddSingleton<ILoggerFactory>(NullLoggerFactory.Instance);
2828
services.AddSingleton<AuthenticationStateProvider, PlaceholderAuthenticationStateProvider>();
2929
services.AddSingleton<IAuthorizationService, PlaceholderAuthorizationService>();
30-
services.AddSingleton<IJSRuntime, PlaceholderJSRuntime>();
3130
services.AddSingleton<NavigationManager, PlaceholderNavigationManager>();
3231
services.AddSingleton<HttpClient, PlaceholderHttpClient>();
3332
services.AddSingleton<IStringLocalizer, PlaceholderStringLocalization>();

src/bunit.web/TestDoubles/JSInterop/MockJSRuntimeInvokeHandler.cs renamed to src/bunit.web/JSInterop/BunitJSInterop.cs

Lines changed: 66 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -5,140 +5,137 @@
55
using System.Threading.Tasks;
66
using Microsoft.JSInterop;
77

8-
namespace Bunit.TestDoubles
8+
namespace Bunit
99
{
1010
/// <summary>
11-
/// Represents an invoke handler for a mock of a <see cref="IJSRuntime"/>.
11+
/// Represents an bUnit's implementation of Blazor's JSInterop.
1212
/// </summary>
13-
public class MockJSRuntimeInvokeHandler
13+
public class BunitJSInterop
1414
{
1515
private readonly Dictionary<string, List<JSRuntimeInvocation>> _invocations = new Dictionary<string, List<JSRuntimeInvocation>>();
16-
private readonly Dictionary<string, List<object>> _plannedInvocations = new Dictionary<string, List<object>>();
17-
private readonly Dictionary<Type, object> _catchAllInvocations = new Dictionary<Type, object>();
16+
private readonly Dictionary<string, List<object>> _handlers = new Dictionary<string, List<object>>();
17+
private readonly Dictionary<Type, object> _catchAllHandlers = new Dictionary<Type, object>();
1818

1919
/// <summary>
2020
/// Gets a dictionary of all <see cref="List{JSRuntimeInvocation}"/> this mock has observed.
2121
/// </summary>
2222
public IReadOnlyDictionary<string, List<JSRuntimeInvocation>> Invocations => _invocations;
2323

2424
/// <summary>
25-
/// Gets whether the mock is running in <see cref="JSRuntimeMockMode.Loose"/> or
26-
/// <see cref="JSRuntimeMockMode.Strict"/>.
25+
/// Gets or sets whether the mock is running in <see cref="JSRuntimeMode.Loose"/> or
26+
/// <see cref="JSRuntimeMode.Strict"/>.
2727
/// </summary>
28-
public JSRuntimeMockMode Mode { get; }
28+
public JSRuntimeMode Mode { get; set; }
2929

3030
/// <summary>
31-
/// Creates a <see cref="MockJSRuntimeInvokeHandler"/>.
31+
/// Gets the mocked <see cref="IJSRuntime"/> instance.
3232
/// </summary>
33-
/// <param name="mode">The <see cref="JSRuntimeMockMode"/> the handler should use.</param>
34-
public MockJSRuntimeInvokeHandler(JSRuntimeMockMode mode = JSRuntimeMockMode.Loose)
35-
{
36-
Mode = mode;
37-
}
33+
/// <returns></returns>
34+
public IJSRuntime JSRuntime { get; }
3835

3936
/// <summary>
40-
/// Gets the mocked <see cref="IJSRuntime"/> instance.
37+
/// Creates a <see cref="BunitJSInterop"/>.
4138
/// </summary>
42-
/// <returns></returns>
43-
public IJSRuntime ToJSRuntime()
39+
public BunitJSInterop()
4440
{
45-
return new MockJSRuntime(this);
41+
Mode = JSRuntimeMode.Strict;
42+
JSRuntime = new BUnitJSRuntime(this);
4643
}
4744

4845
/// <summary>
49-
/// Configure a catch all JSInterop invocation for a specific return type.
46+
/// Configure a catch all JSInterop invocation handler for a specific return type.
47+
/// This will match only on the <typeparamref name="TResult"/>, and any arguments passed to
48+
/// <see cref="IJSRuntime.InvokeAsync{TValue}(string, object[])"/>.
5049
/// </summary>
51-
/// <typeparam name="TResult">The result type of the invocation</typeparam>
52-
/// <returns>A <see cref="JSRuntimeCatchAllPlannedInvocation{TResult}"/>.</returns>
53-
public JSRuntimeCatchAllPlannedInvocation<TResult> Setup<TResult>()
50+
/// <typeparam name="TResult">The result type of the invocation.</typeparam>
51+
/// <returns>A <see cref="JSRuntimeCatchAllInvocationHandler{TResult}"/>.</returns>
52+
public JSRuntimeCatchAllInvocationHandler<TResult> Setup<TResult>()
5453
{
55-
var result = new JSRuntimeCatchAllPlannedInvocation<TResult>();
54+
var result = new JSRuntimeCatchAllInvocationHandler<TResult>();
5655

57-
_catchAllInvocations[typeof(TResult)] = result;
56+
_catchAllHandlers[typeof(TResult)] = result;
5857

5958
return result;
6059
}
6160

6261
/// <summary>
63-
/// Configure a planned JSInterop invocation with the <paramref name="identifier"/> and arguments
62+
/// Configure a JSInterop invocation handler with the <paramref name="identifier"/> and arguments
6463
/// passing the <paramref name="argumentsMatcher"/> test.
6564
/// </summary>
66-
/// <typeparam name="TResult">The result type of the invocation</typeparam>
67-
/// <param name="identifier">The identifier to setup a response for</param>
65+
/// <typeparam name="TResult">The result type of the invocation.</typeparam>
66+
/// <param name="identifier">The identifier to setup a response for.</param>
6867
/// <param name="argumentsMatcher">A matcher that is passed arguments received in invocations to <paramref name="identifier"/>. If it returns true the invocation is matched.</param>
69-
/// <returns>A <see cref="JSRuntimePlannedInvocation{TResult}"/>.</returns>
70-
public JSRuntimePlannedInvocation<TResult> Setup<TResult>(string identifier, Func<IReadOnlyList<object?>, bool> argumentsMatcher)
68+
/// <returns>A <see cref="JSRuntimeInvocationHandler{TResult}"/>.</returns>
69+
public JSRuntimeInvocationHandler<TResult> Setup<TResult>(string identifier, Func<IReadOnlyList<object?>, bool> argumentsMatcher)
7170
{
72-
var result = new JSRuntimePlannedInvocation<TResult>(identifier, argumentsMatcher);
71+
var result = new JSRuntimeInvocationHandler<TResult>(identifier, argumentsMatcher);
7372

74-
AddPlannedInvocation(result);
73+
AddHandler(result);
7574

7675
return result;
7776
}
7877

7978
/// <summary>
80-
/// Configure a planned JSInterop invocation with the <paramref name="identifier"/> and <paramref name="arguments"/>.
79+
/// Configure a JSInterop invocation handler with the <paramref name="identifier"/> and <paramref name="arguments"/>.
8180
/// </summary>
8281
/// <typeparam name="TResult"></typeparam>
83-
/// <param name="identifier">The identifier to setup a response for</param>
82+
/// <param name="identifier">The identifier to setup a response for.</param>
8483
/// <param name="arguments">The arguments that an invocation to <paramref name="identifier"/> should match.</param>
85-
/// <returns>A <see cref="JSRuntimePlannedInvocation{TResult}"/>.</returns>
86-
public JSRuntimePlannedInvocation<TResult> Setup<TResult>(string identifier, params object[] arguments)
84+
/// <returns>A <see cref="JSRuntimeInvocationHandler{TResult}"/>.</returns>
85+
public JSRuntimeInvocationHandler<TResult> Setup<TResult>(string identifier, params object[] arguments)
8786
{
8887
return Setup<TResult>(identifier, args => args.SequenceEqual(arguments));
8988
}
9089

9190
/// <summary>
92-
/// Configure a planned JSInterop invocation with the <paramref name="identifier"/> and arguments
91+
/// Configure a JSInterop invocation handler with the <paramref name="identifier"/> and arguments
9392
/// passing the <paramref name="argumentsMatcher"/> test, that should not receive any result.
9493
/// </summary>
95-
/// <param name="identifier">The identifier to setup a response for</param>
94+
/// <param name="identifier">The identifier to setup a response for.</param>
9695
/// <param name="argumentsMatcher">A matcher that is passed arguments received in invocations to <paramref name="identifier"/>. If it returns true the invocation is matched.</param>
97-
/// <returns>A <see cref="JSRuntimePlannedInvocation"/>.</returns>
98-
public JSRuntimePlannedInvocation SetupVoid(string identifier, Func<IReadOnlyList<object?>, bool> argumentsMatcher)
96+
/// <returns>A <see cref="JSRuntimeInvocationHandler"/>.</returns>
97+
public JSRuntimeInvocationHandler SetupVoid(string identifier, Func<IReadOnlyList<object?>, bool> argumentsMatcher)
9998
{
100-
var result = new JSRuntimePlannedInvocation(identifier, argumentsMatcher);
99+
var result = new JSRuntimeInvocationHandler(identifier, argumentsMatcher);
101100

102-
AddPlannedInvocation(result);
101+
AddHandler(result);
103102

104103
return result;
105104
}
106105

107106
/// <summary>
108-
/// Configure a planned JSInterop invocation with the <paramref name="identifier"/>
107+
/// Configure a JSInterop invocation handler with the <paramref name="identifier"/>
109108
/// and <paramref name="arguments"/>, that should not receive any result.
110109
/// </summary>
111-
/// <param name="identifier">The identifier to setup a response for</param>
110+
/// <param name="identifier">The identifier to setup a response for.</param>
112111
/// <param name="arguments">The arguments that an invocation to <paramref name="identifier"/> should match.</param>
113-
/// <returns>A <see cref="JSRuntimePlannedInvocation"/>.</returns>
114-
public JSRuntimePlannedInvocation SetupVoid(string identifier, params object[] arguments)
112+
/// <returns>A <see cref="JSRuntimeInvocationHandler"/>.</returns>
113+
public JSRuntimeInvocationHandler SetupVoid(string identifier, params object[] arguments)
115114
{
116115
return SetupVoid(identifier, args => args.SequenceEqual(arguments));
117116
}
118117

119118
/// <summary>
120-
/// Configure a catch all JSInterop invocation, that should not receive any result.
119+
/// Configure a catch all JSInterop invocation handler, that should not receive any result.
121120
/// </summary>
122-
/// <returns>A <see cref="JSRuntimeCatchAllPlannedInvocation"/>.</returns>
123-
public JSRuntimeCatchAllPlannedInvocation SetupVoid()
121+
/// <returns>A <see cref="JSRuntimeCatchAllInvocationHandler"/>.</returns>
122+
public JSRuntimeCatchAllInvocationHandler SetupVoid()
124123
{
125-
var result = new JSRuntimeCatchAllPlannedInvocation();
126-
127-
_catchAllInvocations[typeof(object)] = result;
128-
124+
var result = new JSRuntimeCatchAllInvocationHandler();
125+
_catchAllHandlers[typeof(object)] = result;
129126
return result;
130127
}
131128

132-
private void AddPlannedInvocation<TResult>(JSRuntimePlannedInvocation<TResult> planned)
129+
private void AddHandler<TResult>(JSRuntimeInvocationHandler<TResult> handler)
133130
{
134-
if (!_plannedInvocations.ContainsKey(planned.Identifier))
131+
if (!_handlers.ContainsKey(handler.Identifier))
135132
{
136-
_plannedInvocations.Add(planned.Identifier, new List<object>());
133+
_handlers.Add(handler.Identifier, new List<object>());
137134
}
138-
_plannedInvocations[planned.Identifier].Add(planned);
135+
_handlers[handler.Identifier].Add(handler);
139136
}
140137

141-
private void AddInvocation(JSRuntimeInvocation invocation)
138+
private void RegisterInvocation(JSRuntimeInvocation invocation)
142139
{
143140
if (!_invocations.ContainsKey(invocation.Identifier))
144141
{
@@ -147,13 +144,13 @@ private void AddInvocation(JSRuntimeInvocation invocation)
147144
_invocations[invocation.Identifier].Add(invocation);
148145
}
149146

150-
private class MockJSRuntime : IJSRuntime
147+
private class BUnitJSRuntime : IJSRuntime
151148
{
152-
private readonly MockJSRuntimeInvokeHandler _handlers;
149+
private readonly BunitJSInterop _jsInterop;
153150

154-
public MockJSRuntime(MockJSRuntimeInvokeHandler mockJSRuntimeInvokeHandler)
151+
public BUnitJSRuntime(BunitJSInterop bunitJsInterop)
155152
{
156-
_handlers = mockJSRuntimeInvokeHandler;
153+
_jsInterop = bunitJsInterop;
157154
}
158155

159156
public ValueTask<TValue> InvokeAsync<TValue>(string identifier, object?[]? args)
@@ -162,7 +159,7 @@ public ValueTask<TValue> InvokeAsync<TValue>(string identifier, object?[]? args)
162159
public ValueTask<TValue> InvokeAsync<TValue>(string identifier, CancellationToken cancellationToken, object?[]? args)
163160
{
164161
var invocation = new JSRuntimeInvocation(identifier, cancellationToken, args);
165-
_handlers.AddInvocation(invocation);
162+
_jsInterop.RegisterInvocation(invocation);
166163

167164
return TryHandlePlannedInvocation<TValue>(identifier, invocation)
168165
?? new ValueTask<TValue>(default(TValue)!);
@@ -171,9 +168,9 @@ public ValueTask<TValue> InvokeAsync<TValue>(string identifier, CancellationToke
171168
private ValueTask<TValue>? TryHandlePlannedInvocation<TValue>(string identifier, JSRuntimeInvocation invocation)
172169
{
173170
ValueTask<TValue>? result = default;
174-
if (_handlers._plannedInvocations.TryGetValue(identifier, out var plannedInvocations))
171+
if (_jsInterop._handlers.TryGetValue(identifier, out var plannedInvocations))
175172
{
176-
var planned = plannedInvocations.OfType<JSRuntimePlannedInvocationBase<TValue>>()
173+
var planned = plannedInvocations.OfType<JSRuntimeInvocationHandlerBase<TValue>>()
177174
.SingleOrDefault(x => x.Matches(invocation));
178175

179176
if (planned is not null)
@@ -183,20 +180,20 @@ public ValueTask<TValue> InvokeAsync<TValue>(string identifier, CancellationToke
183180
}
184181
}
185182

186-
if (_handlers._catchAllInvocations.TryGetValue(typeof(TValue), out var catchAllInvocation))
183+
if (_jsInterop._catchAllHandlers.TryGetValue(typeof(TValue), out var catchAllInvocation))
187184
{
188-
var planned = catchAllInvocation as JSRuntimePlannedInvocationBase<TValue>;
185+
var planned = catchAllInvocation as JSRuntimeInvocationHandlerBase<TValue>;
189186

190187
if (planned is not null)
191188
{
192-
var task = ((JSRuntimePlannedInvocationBase<TValue>)catchAllInvocation).RegisterInvocation(invocation);
189+
var task = ((JSRuntimeInvocationHandlerBase<TValue>)catchAllInvocation).RegisterInvocation(invocation);
193190
return new ValueTask<TValue>(task);
194191
}
195192
}
196193

197-
if (result is null && _handlers.Mode == JSRuntimeMockMode.Strict)
194+
if (result is null && _jsInterop.Mode == JSRuntimeMode.Strict)
198195
{
199-
throw new UnplannedJSInvocationException(invocation);
196+
throw new JSRuntimeUnhandledInvocationException(invocation);
200197
}
201198

202199
return result;

src/bunit.web/TestDoubles/JSInterop/JSInvokeCountExpectedException.cs renamed to src/bunit.web/JSInterop/JSInvokeCountExpectedException.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
using System;
22
using System.Runtime.Serialization;
33

4-
namespace Bunit.TestDoubles
4+
namespace Bunit
55
{
66
/// <summary>
7-
/// Represents a number of unexpected invocation to a <see cref="MockJSRuntimeInvokeHandler"/>.
7+
/// Represents a number of unexpected invocation to a <see cref="BunitJSInterop"/>.
88
/// </summary>
99
[Serializable]
1010
public sealed class JSInvokeCountExpectedException : Exception

src/bunit.web/TestDoubles/JSInterop/JSRuntimeAssertExtensions.cs renamed to src/bunit.web/JSInterop/JSRuntimeAssertExtensions.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
using Bunit.Asserting;
55
using Microsoft.AspNetCore.Components;
66

7-
namespace Bunit.TestDoubles
7+
namespace Bunit
88
{
99
/// <summary>
1010
/// Assert extensions for JSRuntimeMock
@@ -17,7 +17,7 @@ public static class JSRuntimeAssertExtensions
1717
/// <param name="handler">Handler to verify against.</param>
1818
/// <param name="identifier">Identifier of invocation that should not have happened.</param>
1919
/// <param name="userMessage">A custom user message to display if the assertion fails.</param>
20-
public static void VerifyNotInvoke(this MockJSRuntimeInvokeHandler handler, string identifier, string? userMessage = null)
20+
public static void VerifyNotInvoke(this BunitJSInterop handler, string identifier, string? userMessage = null)
2121
{
2222
if (handler is null)
2323
throw new ArgumentNullException(nameof(handler));
@@ -34,7 +34,7 @@ public static void VerifyNotInvoke(this MockJSRuntimeInvokeHandler handler, stri
3434
/// <param name="identifier">Identifier of invocation that should have been invoked.</param>
3535
/// <param name="userMessage">A custom user message to display if the assertion fails.</param>
3636
/// <returns>The <see cref="JSRuntimeInvocation"/>.</returns>
37-
public static JSRuntimeInvocation VerifyInvoke(this MockJSRuntimeInvokeHandler handler, string identifier, string? userMessage = null)
37+
public static JSRuntimeInvocation VerifyInvoke(this BunitJSInterop handler, string identifier, string? userMessage = null)
3838
=> handler.VerifyInvoke(identifier, 1, userMessage)[0];
3939

4040
/// <summary>
@@ -45,7 +45,7 @@ public static JSRuntimeInvocation VerifyInvoke(this MockJSRuntimeInvokeHandler h
4545
/// <param name="calledTimes">The number of times the invocation is expected to have been called.</param>
4646
/// <param name="userMessage">A custom user message to display if the assertion fails.</param>
4747
/// <returns>The <see cref="JSRuntimeInvocation"/>.</returns>
48-
public static IReadOnlyList<JSRuntimeInvocation> VerifyInvoke(this MockJSRuntimeInvokeHandler handler, string identifier, int calledTimes, string? userMessage = null)
48+
public static IReadOnlyList<JSRuntimeInvocation> VerifyInvoke(this BunitJSInterop handler, string identifier, int calledTimes, string? userMessage = null)
4949
{
5050
if (handler is null)
5151
throw new ArgumentNullException(nameof(handler));
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
namespace Bunit
2+
{
3+
/// <summary>
4+
/// Represents a handler for an invocation of a JavaScript function, which matches all types of arguments,
5+
/// which returns nothing.
6+
/// </summary>
7+
public class JSRuntimeCatchAllInvocationHandler : JSRuntimeInvocationHandlerBase<object>
8+
{
9+
internal JSRuntimeCatchAllInvocationHandler() { }
10+
11+
internal override bool Matches(JSRuntimeInvocation invocation)
12+
{
13+
return true;
14+
}
15+
16+
/// <summary>
17+
/// Completes the current awaiting void invocation request.
18+
/// </summary>
19+
public void SetVoid() => SetResultBase(default!);
20+
}
21+
22+
/// <summary>
23+
/// Represents a handler for an invocation of a JavaScript function, which matches all types of arguments,
24+
/// that returns a <typeparamref name="TResult"/>.
25+
/// </summary>
26+
/// <typeparam name="TResult"></typeparam>
27+
public class JSRuntimeCatchAllInvocationHandler<TResult> : JSRuntimeInvocationHandlerBase<TResult>
28+
{
29+
internal JSRuntimeCatchAllInvocationHandler()
30+
{ }
31+
32+
internal override bool Matches(JSRuntimeInvocation invocation) => true;
33+
34+
/// <summary>
35+
/// Sets the <typeparamref name="TResult"/> result that invocations will receive.
36+
/// </summary>
37+
/// <param name="result"></param>
38+
public void SetResult(TResult result) => SetResultBase(result);
39+
}
40+
}

src/bunit.web/TestDoubles/JSInterop/JSRuntimeInvocation.cs renamed to src/bunit.web/JSInterop/JSRuntimeInvocation.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
using System.Diagnostics.CodeAnalysis;
44
using System.Threading;
55

6-
namespace Bunit.TestDoubles
6+
namespace Bunit
77
{
88
/// <summary>
99
/// Represents an invocation of JavaScript via the JSRuntime Mock
1010
/// </summary>
11-
[SuppressMessage("Design", "CA1068:CancellationToken parameters must come last", Justification = "<Pending>")]
11+
[SuppressMessage("Design", "CA1068:CancellationToken parameters must come last", Justification = "Following Blazors design")]
1212
public readonly struct JSRuntimeInvocation : IEquatable<JSRuntimeInvocation>
1313
{
1414
/// <summary>

0 commit comments

Comments
 (0)