Skip to content

Commit 169ddad

Browse files
committed
fix: changed TestServiceProvider to validate scopes like Blazor's does. Closes #461
1 parent 877dfde commit 169ddad

4 files changed

Lines changed: 97 additions & 19 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ List of fixes in this release.
5050
### Changed
5151

5252
- Changed bunit.template such that created projects only reference the bUnit package. Bumped other referenced packages to latest version.
53+
- Changed TestServiceProvider to validate scopes of registered services, such that it behaves like the service provider (default IoC container) in Blazor.
5354

5455
## [1.0.16]
5556

src/bunit.core/TestServiceProvider.cs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ namespace Bunit
1313
public sealed class TestServiceProvider : IServiceProvider, IServiceCollection, IDisposable
1414
{
1515
private readonly IServiceCollection serviceCollection;
16-
private ServiceProvider? serviceProvider;
16+
private IServiceProvider? rootServiceProvider;
17+
private IServiceScope? serviceScope;
18+
private IServiceProvider? serviceProvider;
1719
private IServiceProvider? fallbackServiceProvider;
1820

1921
/// <summary>
@@ -85,7 +87,9 @@ public object GetService(Type serviceType)
8587
if (serviceProvider is null)
8688
{
8789
serviceCollection.AddSingleton<TestServiceProvider>(this);
88-
serviceProvider = serviceCollection.BuildServiceProvider();
90+
rootServiceProvider = serviceCollection.BuildServiceProvider(validateScopes: true);
91+
serviceScope = rootServiceProvider.CreateScope();
92+
serviceProvider = serviceScope.ServiceProvider;
8993
}
9094

9195
var result = serviceProvider.GetService(serviceType);
@@ -104,15 +108,11 @@ public object GetService(Type serviceType)
104108

105109
/// <inheritdoc/>
106110
public void Dispose()
107-
{
108-
if (serviceProvider is null) return;
109-
110-
var disposedTask = serviceProvider.DisposeAsync().AsTask();
111-
112-
if (!disposedTask.IsCompleted)
113-
disposedTask.GetAwaiter().GetResult();
114-
115-
serviceProvider.Dispose();
111+
{
112+
(serviceScope as IAsyncDisposable)?.DisposeAsync().AsTask().GetAwaiter().GetResult();
113+
serviceScope?.Dispose();
114+
(rootServiceProvider as IAsyncDisposable)?.DisposeAsync().AsTask().GetAwaiter().GetResult();
115+
(rootServiceProvider as IDisposable)?.Dispose();
116116
}
117117

118118
/// <inheritdoc/>

tests/bunit.core.tests/TestServiceProviderTest.cs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections;
33
using System.Linq;
4+
using System.Threading.Tasks;
45
using Microsoft.AspNetCore.Components;
56
using Microsoft.Extensions.DependencyInjection;
67
using Shouldly;
@@ -209,5 +210,51 @@ public void Test030()
209210
// Act and assert
210211
Should.NotThrow(() => ctx.RenderComponent<DummyComponentWhichRequiresDummyService>());
211212
}
213+
214+
[Fact(DisplayName = "Can correctly resolve and dispose of scoped disposable service")]
215+
public void Test031()
216+
{
217+
var sut = new TestServiceProvider();
218+
sut.AddScoped<DisposableService>();
219+
var disposable = sut.GetService<DisposableService>();
220+
221+
sut.Dispose();
222+
223+
disposable.IsDisposed.ShouldBeTrue();
224+
}
225+
226+
[Fact(DisplayName = "Can correctly resolve and dispose of transient disposable service")]
227+
public void Test032()
228+
{
229+
var sut = new TestServiceProvider();
230+
sut.AddTransient<DisposableService>();
231+
var disposable = sut.GetService<DisposableService>();
232+
233+
sut.Dispose();
234+
235+
disposable.IsDisposed.ShouldBeTrue();
236+
}
237+
238+
[Fact(DisplayName = "Can correctly resolve and dispose of singleton disposable service")]
239+
public void Test033()
240+
{
241+
var sut = new TestServiceProvider();
242+
sut.AddSingleton<DisposableService>();
243+
var disposable = sut.GetService<DisposableService>();
244+
245+
sut.Dispose();
246+
247+
disposable.IsDisposed.ShouldBeTrue();
248+
}
249+
250+
private sealed class DisposableService : IDisposable
251+
{
252+
public bool IsDisposed { get; private set; }
253+
254+
public void Dispose()
255+
{
256+
IsDisposed = true;
257+
}
258+
}
212259
}
213260
}

tests/bunit.core.tests/TestServiceProviderTest.net5.cs

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
#if !NETCOREAPP3_1
1+
#if NET5_0_OR_GREATER
22
using System;
3-
using System.Diagnostics.CodeAnalysis;
43
using System.Threading.Tasks;
54
using Microsoft.Extensions.DependencyInjection;
65
using Shouldly;
@@ -10,20 +9,51 @@ namespace Bunit
109
{
1110
public partial class TestServiceProviderTest
1211
{
13-
[Fact(DisplayName = "Can correctly dispose of async disposable service")]
14-
[SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "Point of test is to verify explicit call to Dispose doesn't throw.")]
12+
[Fact(DisplayName = "Can correctly resolve and dispose of scoped disposable service")]
1513
public void Net5Test001()
1614
{
1715
var sut = new TestServiceProvider();
1816
sut.AddScoped<AsyncDisposableService>();
19-
sut.GetService<AsyncDisposableService>();
17+
var asyncDisposable = sut.GetService<AsyncDisposableService>();
2018

21-
Should.NotThrow(() => sut.Dispose());
19+
sut.Dispose();
20+
21+
asyncDisposable.IsDisposed.ShouldBeTrue();
22+
}
23+
24+
[Fact(DisplayName = "Can correctly resolve and dispose of transient disposable service")]
25+
public void Net5Test002()
26+
{
27+
var sut = new TestServiceProvider();
28+
sut.AddTransient<AsyncDisposableService>();
29+
var asyncDisposable = sut.GetService<AsyncDisposableService>();
30+
31+
sut.Dispose();
32+
33+
asyncDisposable.IsDisposed.ShouldBeTrue();
2234
}
2335

24-
private class AsyncDisposableService : IAsyncDisposable
36+
[Fact(DisplayName = "Can correctly resolve and dispose of singleton disposable service")]
37+
public void Net5Test003()
2538
{
26-
public ValueTask DisposeAsync() => ValueTask.CompletedTask;
39+
var sut = new TestServiceProvider();
40+
sut.AddSingleton<AsyncDisposableService>();
41+
var asyncDisposable = sut.GetService<AsyncDisposableService>();
42+
43+
sut.Dispose();
44+
45+
asyncDisposable.IsDisposed.ShouldBeTrue();
46+
}
47+
48+
private sealed class AsyncDisposableService : IAsyncDisposable
49+
{
50+
public bool IsDisposed { get; private set; }
51+
52+
public ValueTask DisposeAsync()
53+
{
54+
IsDisposed = true;
55+
return ValueTask.CompletedTask;
56+
}
2757
}
2858
}
2959
}

0 commit comments

Comments
 (0)