From 43d4d8bf54c3d76b00e8202d01e875c8f796f267 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20B=C3=BClow?= Date: Tue, 10 Oct 2023 14:53:12 +0200 Subject: [PATCH 01/13] Allow custom service providers like autofac --- src/bunit.core/TestServiceProvider.cs | 88 +++++++++- .../TestServiceProviderTest.cs | 160 +++++++++++++----- 2 files changed, 203 insertions(+), 45 deletions(-) diff --git a/src/bunit.core/TestServiceProvider.cs b/src/bunit.core/TestServiceProvider.cs index 7af3a9148..785352b97 100644 --- a/src/bunit.core/TestServiceProvider.cs +++ b/src/bunit.core/TestServiceProvider.cs @@ -15,6 +15,7 @@ public sealed class TestServiceProvider : IServiceProvider, IServiceCollection, private IServiceProvider? serviceProvider; private IServiceProvider? fallbackServiceProvider; private ServiceProviderOptions options = DefaultServiceProviderOptions; + private Func? buildServiceProviderDelegate; /// /// Gets a value indicating whether this has been initialized, and @@ -61,7 +62,74 @@ private TestServiceProvider(IServiceCollection initialServiceCollection, bool in { serviceCollection = initialServiceCollection; if (initializeProvider) - serviceProvider = serviceCollection.BuildServiceProvider(); + { + InitializeProvider(); + } + } + + /// + /// Use a custom service provider factory for creating the underlying IServiceProvider. + /// + /// custom service provider factory + public void UseServiceProviderFactory(Func? serviceProviderFactory) + { + if (serviceProviderFactory is null) + { + throw new ArgumentNullException(nameof(serviceProviderFactory)); + } + + buildServiceProviderDelegate = BuildServiceProvider; + + IServiceProvider BuildServiceProvider() + => serviceProviderFactory(serviceCollection); + } + + /// + /// Use a custom service provider factory for creating the underlying IServiceProvider. + /// + /// + /// Type of the container builder. + /// See + /// + /// custom service provider factory + /// builder configuration action + public void UseServiceProviderFactory(IServiceProviderFactory serviceProviderFactory, Action? configure = null) + { + if (serviceProviderFactory is null) + { + throw new ArgumentNullException(nameof(serviceProviderFactory)); + } + + buildServiceProviderDelegate = BuildServiceProvider; + + IServiceProvider BuildServiceProvider() + { + var containerBuilder = serviceProviderFactory.CreateBuilder(serviceCollection); + configure?.Invoke(containerBuilder); + return serviceProviderFactory.CreateServiceProvider(containerBuilder); + } + } + + /// + /// Creates the underlying service provider. Throws if it was already build. + /// Automatically called while getting a service if unitialized. + /// No longer will accept calls to the AddService's methods. + /// See + /// +#if !NETSTANDARD2_1 + [MemberNotNull(nameof(serviceProvider))] +#endif + public void InitializeProvider() + { + CheckInitializedAndThrow(); + + _ = serviceCollection.AddSingleton(this); + rootServiceProvider = (buildServiceProviderDelegate ?? BuildServiceProvider)(); + serviceScope = rootServiceProvider.CreateScope(); + serviceProvider = serviceScope.ServiceProvider; + + IServiceProvider BuildServiceProvider() + => serviceCollection.BuildServiceProvider(options); } /// @@ -93,16 +161,15 @@ public object GetService(Type serviceType) { if (serviceProvider is null) { - serviceCollection.AddSingleton(this); - rootServiceProvider = serviceCollection.BuildServiceProvider(options); - serviceScope = rootServiceProvider.CreateScope(); - serviceProvider = serviceScope.ServiceProvider; + InitializeProvider(); } - var result = serviceProvider.GetService(serviceType); + var result = serviceProvider!.GetService(serviceType); if (result is null && fallbackServiceProvider is not null) + { result = fallbackServiceProvider.GetService(serviceType); + } return result; } @@ -113,25 +180,32 @@ public object GetService(Type serviceType) /// IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - /// public void Dispose() { if (serviceScope is IDisposable serviceScopeDisposable) + { serviceScopeDisposable.Dispose(); + } if (rootServiceProvider is IDisposable rootServiceProviderDisposable) + { rootServiceProviderDisposable.Dispose(); + } } /// public async ValueTask DisposeAsync() { if (serviceScope is IAsyncDisposable serviceScopeAsync) + { await serviceScopeAsync.DisposeAsync(); + } if (rootServiceProvider is IAsyncDisposable rootServiceProviderAsync) + { await rootServiceProviderAsync.DisposeAsync(); + } } /// diff --git a/tests/bunit.core.tests/TestServiceProviderTest.cs b/tests/bunit.core.tests/TestServiceProviderTest.cs index 047d3fc5d..0045734d4 100644 --- a/tests/bunit.core.tests/TestServiceProviderTest.cs +++ b/tests/bunit.core.tests/TestServiceProviderTest.cs @@ -1,5 +1,3 @@ -using System.Collections; - namespace Bunit; public partial class TestServiceProviderTest @@ -16,7 +14,7 @@ public void Test001() public void Test002() { var services = new ServiceCollection(); - services.AddSingleton(new DummyService()); + _ = services.AddSingleton(new DummyService()); using var sut = new TestServiceProvider(services); sut.Count.ShouldBe(1); @@ -26,9 +24,10 @@ public void Test002() [Fact(DisplayName = "Services can be registered in the provider like a normal service collection")] public void Test010() { - using var sut = new TestServiceProvider(); - - sut.Add(new ServiceDescriptor(typeof(DummyService), new DummyService())); + using var sut = new TestServiceProvider + { + new ServiceDescriptor(typeof(DummyService), new DummyService()) + }; sut.Insert(0, new ServiceDescriptor(typeof(AnotherDummyService), new AnotherDummyService())); sut[1] = new ServiceDescriptor(typeof(DummyService), new DummyService()); @@ -49,7 +48,7 @@ public void Test011() sut.Add(anotherDescriptor); sut.Add(oneMoreDescriptor); - sut.Remove(descriptor); + _ = sut.Remove(descriptor); sut.Count.ShouldBe(2); sut.RemoveAt(1); @@ -72,7 +71,7 @@ public void Test012() sut.CopyTo(copyToTarget, 0); copyToTarget[0].ShouldBe(descriptor); sut.IsReadOnly.ShouldBeFalse(); - ((IEnumerable)sut).OfType().Count().ShouldBe(1); + sut.OfType().Count().ShouldBe(1); } [Fact(DisplayName = "After the first service is requested, " + @@ -82,18 +81,18 @@ public void Test013() var descriptor = new ServiceDescriptor(typeof(AnotherDummyService), new AnotherDummyService()); using var sut = new TestServiceProvider(); - sut.AddSingleton(new DummyService()); - sut.GetService(); + _ = sut.AddSingleton(new DummyService()); + _ = sut.GetService(); // Try adding - Should.Throw(() => sut.Add(descriptor)); - Should.Throw(() => sut.Insert(0, descriptor)); - Should.Throw(() => sut[0] = descriptor); + _ = Should.Throw(() => sut.Add(descriptor)); + _ = Should.Throw(() => sut.Insert(0, descriptor)); + _ = Should.Throw(() => sut[0] = descriptor); // Try removing - Should.Throw(() => sut.Remove(descriptor)); - Should.Throw(() => sut.RemoveAt(0)); - Should.Throw(() => sut.Clear()); + _ = Should.Throw(() => sut.Remove(descriptor)); + _ = Should.Throw(() => sut.RemoveAt(0)); + _ = Should.Throw(sut.Clear); // Verify state sut.IsProviderInitialized.ShouldBeTrue(); @@ -105,7 +104,7 @@ public void Test020() { using var sut = new TestServiceProvider(); var expected = new DummyService(); - sut.AddSingleton(expected); + _ = sut.AddSingleton(expected); var actual = sut.GetService(); @@ -131,14 +130,14 @@ public void Test022() var result = sut.GetService(typeof(object)); Assert.NotNull(result); - Assert.IsType(result); + _ = Assert.IsType(result); } [Fact(DisplayName = "Register fallback service with null value")] public void Test023() { using var sut = new TestServiceProvider(); - Assert.Throws(() => sut.AddFallbackServiceProvider(null!)); + _ = Assert.Throws(() => sut.AddFallbackServiceProvider(null!)); } [Fact(DisplayName = "Service provider returns value before fallback service provider")] @@ -147,14 +146,14 @@ public void Test024() const string exceptionStringResult = "exceptionStringResult"; using var sut = new TestServiceProvider(); - sut.AddSingleton(exceptionStringResult); + _ = sut.AddSingleton(exceptionStringResult); sut.AddFallbackServiceProvider(new FallbackServiceProvider()); var stringResult = sut.GetService(typeof(string)); Assert.Equal(exceptionStringResult, stringResult); var fallbackResult = sut.GetService(typeof(DummyService)); - Assert.IsType(fallbackResult); + _ = Assert.IsType(fallbackResult); } [Fact(DisplayName = "Latest fallback provider is used")] @@ -166,7 +165,7 @@ public void Test025() var result = sut.GetService(typeof(object)); - Assert.IsType(result); + _ = Assert.IsType(result); } [Fact(DisplayName = "Fallback service provider can be used to resolve services required by components")] @@ -180,14 +179,14 @@ public void Test030() ctx.Services.AddFallbackServiceProvider(fallbackServiceProvider); // Act and assert - Should.NotThrow(() => ctx.RenderComponent()); + _ = Should.NotThrow(() => ctx.RenderComponent()); } [Fact(DisplayName = "Can correctly resolve and dispose of scoped disposable service")] public void Test031() { var sut = new TestServiceProvider(); - sut.AddScoped(); + _ = sut.AddScoped(); var disposable = sut.GetService(); sut.Dispose(); @@ -199,7 +198,7 @@ public void Test031() public void Test032() { var sut = new TestServiceProvider(); - sut.AddTransient(); + _ = sut.AddTransient(); var disposable = sut.GetService(); sut.Dispose(); @@ -211,7 +210,7 @@ public void Test032() public void Test033() { var sut = new TestServiceProvider(); - sut.AddSingleton(); + _ = sut.AddSingleton(); var disposable = sut.GetService(); sut.Dispose(); @@ -228,11 +227,11 @@ public void Test035() ValidateOnBuild = true, ValidateScopes = true }; - sut.AddSingleton(); - sut.AddSingleton(); - var action = () => sut.GetRequiredService(); + _ = sut.AddSingleton(); + _ = sut.AddSingleton(); + var action = sut.GetRequiredService; - action.ShouldThrow("Some services are not able to be constructed (Error while validating the service descriptor"); + _ = action.ShouldThrow("Some services are not able to be constructed (Error while validating the service descriptor"); } [Fact(DisplayName = "Does not validate all dependencies can be created when the first service is requested, if ServiceProviderOptions.ValidateOnBuild is false")] @@ -244,24 +243,63 @@ public void Test036() ValidateOnBuild = false, ValidateScopes = true }; - sut.AddSingleton(); - sut.AddSingleton(); + _ = sut.AddSingleton(); + _ = sut.AddSingleton(); var result = sut.GetRequiredService(); - result.ShouldNotBeNull(); + _ = result.ShouldNotBeNull(); } [Fact(DisplayName = "Does not validate all dependencies can be created when the first service is requested, if no ServiceProviderOptions is provided (backwards compatibility)")] public void Test037() { using var sut = new TestServiceProvider(); - sut.AddSingleton(); - sut.AddSingleton(); + _ = sut.AddSingleton(); + _ = sut.AddSingleton(); + + var result = sut.GetRequiredService(); + + _ = result.ShouldNotBeNull(); + } + + [Fact(DisplayName = "Test custom service provider factory")] + public void Test038() + { + using var sut = new TestServiceProvider(); + _ = sut.AddSingleton(); + + var dummyServiceProviderFactory = new DummyServiceProviderFactory(); + + sut.UseServiceProviderFactory(dummyServiceProviderFactory); + + var result = sut.GetRequiredService(); + + _ = result.ShouldNotBeNull(); + + _ = dummyServiceProviderFactory.TestContainerBuilder.ShouldNotBeNull(); + _ = dummyServiceProviderFactory.TestContainerBuilder.TestServiceProvider.ShouldNotBeNull(); + dummyServiceProviderFactory.TestContainerBuilder.TestServiceProvider.ResolvedTestServices.ShouldContain(result); + dummyServiceProviderFactory.TestContainerBuilder.TestServiceProvider.ResolvedTestServices.Count.ShouldBe(1); + } + + [Fact(DisplayName = "Test custom service provider factory as delegate")] + public void Test039() + { + using var sut = new TestServiceProvider(); + _ = sut.AddSingleton(); + + DummyServiceProvider dummyServiceProvider = null; + + sut.UseServiceProviderFactory(x => dummyServiceProvider = new DummyServiceProvider(x)); var result = sut.GetRequiredService(); - result.ShouldNotBeNull(); + _ = result.ShouldNotBeNull(); + + _ = dummyServiceProvider.ShouldNotBeNull(); + dummyServiceProvider.ResolvedTestServices.ShouldContain(result); + dummyServiceProvider.ResolvedTestServices.Count.ShouldBe(1); } private sealed class DummyService { } @@ -296,9 +334,55 @@ private sealed class DisposableService : IDisposable { public bool IsDisposed { get; private set; } - public void Dispose() + public void Dispose() => IsDisposed = true; + } + + private sealed class DummyServiceProvider : IServiceProvider, IServiceScopeFactory, IServiceScope + { + private readonly IServiceCollection serviceDescriptors; + + public readonly List ResolvedTestServices = new(); + + public DummyServiceProvider(IServiceCollection serviceDescriptors) + => this.serviceDescriptors = serviceDescriptors; + + public object? GetService(Type serviceType) { - IsDisposed = true; + if (serviceType == typeof(IServiceScope) || serviceType == typeof(IServiceScopeFactory)) + { + return this; + } + + var result = Activator.CreateInstance(serviceDescriptors.Single(x => x.ServiceType == serviceType).ImplementationType); + ResolvedTestServices.Add(result); + return result; } + + void IDisposable.Dispose() { } + public IServiceScope CreateScope() => this; + IServiceProvider IServiceScope.ServiceProvider => this; + + } + + private sealed class DummyServiceProviderFactoryContainerBuilder + { + private readonly IServiceCollection serviceDescriptors; + + public DummyServiceProvider? TestServiceProvider { get; private set; } + + public DummyServiceProviderFactoryContainerBuilder(IServiceCollection serviceDescriptors) => this.serviceDescriptors = serviceDescriptors; + + public IServiceProvider Build() => TestServiceProvider = new DummyServiceProvider(serviceDescriptors); + } + + private sealed class DummyServiceProviderFactory : IServiceProviderFactory + { + public DummyServiceProviderFactoryContainerBuilder TestContainerBuilder { get; private set; } + + public DummyServiceProviderFactoryContainerBuilder CreateBuilder(IServiceCollection services) + => TestContainerBuilder = new DummyServiceProviderFactoryContainerBuilder(services); + + public IServiceProvider CreateServiceProvider(DummyServiceProviderFactoryContainerBuilder containerBuilder) + => containerBuilder.Build(); } } From 513877b0049d6b43a5c96f670de1c6bcfa7768c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20B=C3=BClow?= Date: Tue, 10 Oct 2023 15:03:31 +0200 Subject: [PATCH 02/13] Allow custom service providers like autofac update CHANGELOG.md --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 64776184c..14b3fa3b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,10 +6,14 @@ All notable changes to **bUnit** will be documented in this file. The project ad ## [Unreleased] -## Fixed +### Fixed - When the `TestContext` was disposed, it disposed of all services via the service provider. However, if there were ongoing renders happening, this could cause inconsistent state in the render tree, since the `TestRenderer` could try to access the service provider to instantiate components. This release changes the dispose phase such that the renderer gets disposed first, then the service provider. The disposal of any services that implement `IAsyncDisposable` is now also awaited. Fixed by [@egil](https://github.com/egil) and [@linkdotnet](https://github.com/linkdotnet). Reported by [@BenSchoen](https://github.com/BenSchoen) in https://github.com/bUnit-dev/bUnit/issues/1227. + +### Added + +- Support for custom service provider factories (IServiceProviderFactory). This enables the use of Autofac and other frameworks for dependency injection like on real world ASP.NET Core / Blazor projects. ## [1.23.9] - 2023-09-06 From 57f38c843cac984c51ad982a63e9031842cf5bc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20B=C3=BClow?= Date: Tue, 10 Oct 2023 15:16:23 +0200 Subject: [PATCH 03/13] Allow custom service providers like autofac Revert code changes that were done by automatic clean up. --- src/bunit.core/TestServiceProvider.cs | 16 +--- .../TestServiceProviderTest.cs | 92 ++++++++++--------- 2 files changed, 48 insertions(+), 60 deletions(-) diff --git a/src/bunit.core/TestServiceProvider.cs b/src/bunit.core/TestServiceProvider.cs index 785352b97..f427014b8 100644 --- a/src/bunit.core/TestServiceProvider.cs +++ b/src/bunit.core/TestServiceProvider.cs @@ -62,9 +62,7 @@ private TestServiceProvider(IServiceCollection initialServiceCollection, bool in { serviceCollection = initialServiceCollection; if (initializeProvider) - { InitializeProvider(); - } } /// @@ -123,7 +121,7 @@ public void InitializeProvider() { CheckInitializedAndThrow(); - _ = serviceCollection.AddSingleton(this); + serviceCollection.AddSingleton(this); rootServiceProvider = (buildServiceProviderDelegate ?? BuildServiceProvider)(); serviceScope = rootServiceProvider.CreateScope(); serviceProvider = serviceScope.ServiceProvider; @@ -160,16 +158,12 @@ public object GetService(Type serviceType) private object? GetServiceInternal(Type serviceType) { if (serviceProvider is null) - { InitializeProvider(); - } var result = serviceProvider!.GetService(serviceType); if (result is null && fallbackServiceProvider is not null) - { result = fallbackServiceProvider.GetService(serviceType); - } return result; } @@ -184,28 +178,20 @@ public object GetService(Type serviceType) public void Dispose() { if (serviceScope is IDisposable serviceScopeDisposable) - { serviceScopeDisposable.Dispose(); - } if (rootServiceProvider is IDisposable rootServiceProviderDisposable) - { rootServiceProviderDisposable.Dispose(); - } } /// public async ValueTask DisposeAsync() { if (serviceScope is IAsyncDisposable serviceScopeAsync) - { await serviceScopeAsync.DisposeAsync(); - } if (rootServiceProvider is IAsyncDisposable rootServiceProviderAsync) - { await rootServiceProviderAsync.DisposeAsync(); - } } /// diff --git a/tests/bunit.core.tests/TestServiceProviderTest.cs b/tests/bunit.core.tests/TestServiceProviderTest.cs index 0045734d4..05ee9f4c1 100644 --- a/tests/bunit.core.tests/TestServiceProviderTest.cs +++ b/tests/bunit.core.tests/TestServiceProviderTest.cs @@ -1,3 +1,5 @@ +using System.Collections; + namespace Bunit; public partial class TestServiceProviderTest @@ -14,7 +16,7 @@ public void Test001() public void Test002() { var services = new ServiceCollection(); - _ = services.AddSingleton(new DummyService()); + services.AddSingleton(new DummyService()); using var sut = new TestServiceProvider(services); sut.Count.ShouldBe(1); @@ -24,10 +26,9 @@ public void Test002() [Fact(DisplayName = "Services can be registered in the provider like a normal service collection")] public void Test010() { - using var sut = new TestServiceProvider - { - new ServiceDescriptor(typeof(DummyService), new DummyService()) - }; + using var sut = new TestServiceProvider(); + + sut.Add(new ServiceDescriptor(typeof(DummyService), new DummyService())); sut.Insert(0, new ServiceDescriptor(typeof(AnotherDummyService), new AnotherDummyService())); sut[1] = new ServiceDescriptor(typeof(DummyService), new DummyService()); @@ -48,7 +49,7 @@ public void Test011() sut.Add(anotherDescriptor); sut.Add(oneMoreDescriptor); - _ = sut.Remove(descriptor); + sut.Remove(descriptor); sut.Count.ShouldBe(2); sut.RemoveAt(1); @@ -71,7 +72,7 @@ public void Test012() sut.CopyTo(copyToTarget, 0); copyToTarget[0].ShouldBe(descriptor); sut.IsReadOnly.ShouldBeFalse(); - sut.OfType().Count().ShouldBe(1); + ((IEnumerable)sut).OfType().Count().ShouldBe(1); } [Fact(DisplayName = "After the first service is requested, " + @@ -81,18 +82,18 @@ public void Test013() var descriptor = new ServiceDescriptor(typeof(AnotherDummyService), new AnotherDummyService()); using var sut = new TestServiceProvider(); - _ = sut.AddSingleton(new DummyService()); - _ = sut.GetService(); + sut.AddSingleton(new DummyService()); + sut.GetService(); // Try adding - _ = Should.Throw(() => sut.Add(descriptor)); - _ = Should.Throw(() => sut.Insert(0, descriptor)); - _ = Should.Throw(() => sut[0] = descriptor); + Should.Throw(() => sut.Add(descriptor)); + Should.Throw(() => sut.Insert(0, descriptor)); + Should.Throw(() => sut[0] = descriptor); // Try removing - _ = Should.Throw(() => sut.Remove(descriptor)); - _ = Should.Throw(() => sut.RemoveAt(0)); - _ = Should.Throw(sut.Clear); + Should.Throw(() => sut.Remove(descriptor)); + Should.Throw(() => sut.RemoveAt(0)); + Should.Throw(() => sut.Clear()); // Verify state sut.IsProviderInitialized.ShouldBeTrue(); @@ -104,7 +105,7 @@ public void Test020() { using var sut = new TestServiceProvider(); var expected = new DummyService(); - _ = sut.AddSingleton(expected); + sut.AddSingleton(expected); var actual = sut.GetService(); @@ -130,14 +131,14 @@ public void Test022() var result = sut.GetService(typeof(object)); Assert.NotNull(result); - _ = Assert.IsType(result); + Assert.IsType(result); } [Fact(DisplayName = "Register fallback service with null value")] public void Test023() { using var sut = new TestServiceProvider(); - _ = Assert.Throws(() => sut.AddFallbackServiceProvider(null!)); + Assert.Throws(() => sut.AddFallbackServiceProvider(null!)); } [Fact(DisplayName = "Service provider returns value before fallback service provider")] @@ -146,14 +147,14 @@ public void Test024() const string exceptionStringResult = "exceptionStringResult"; using var sut = new TestServiceProvider(); - _ = sut.AddSingleton(exceptionStringResult); + sut.AddSingleton(exceptionStringResult); sut.AddFallbackServiceProvider(new FallbackServiceProvider()); var stringResult = sut.GetService(typeof(string)); Assert.Equal(exceptionStringResult, stringResult); var fallbackResult = sut.GetService(typeof(DummyService)); - _ = Assert.IsType(fallbackResult); + Assert.IsType(fallbackResult); } [Fact(DisplayName = "Latest fallback provider is used")] @@ -165,7 +166,7 @@ public void Test025() var result = sut.GetService(typeof(object)); - _ = Assert.IsType(result); + Assert.IsType(result); } [Fact(DisplayName = "Fallback service provider can be used to resolve services required by components")] @@ -179,14 +180,14 @@ public void Test030() ctx.Services.AddFallbackServiceProvider(fallbackServiceProvider); // Act and assert - _ = Should.NotThrow(() => ctx.RenderComponent()); + Should.NotThrow(() => ctx.RenderComponent()); } [Fact(DisplayName = "Can correctly resolve and dispose of scoped disposable service")] public void Test031() { var sut = new TestServiceProvider(); - _ = sut.AddScoped(); + sut.AddScoped(); var disposable = sut.GetService(); sut.Dispose(); @@ -198,7 +199,7 @@ public void Test031() public void Test032() { var sut = new TestServiceProvider(); - _ = sut.AddTransient(); + sut.AddTransient(); var disposable = sut.GetService(); sut.Dispose(); @@ -210,7 +211,7 @@ public void Test032() public void Test033() { var sut = new TestServiceProvider(); - _ = sut.AddSingleton(); + sut.AddSingleton(); var disposable = sut.GetService(); sut.Dispose(); @@ -227,11 +228,11 @@ public void Test035() ValidateOnBuild = true, ValidateScopes = true }; - _ = sut.AddSingleton(); - _ = sut.AddSingleton(); - var action = sut.GetRequiredService; + sut.AddSingleton(); + sut.AddSingleton(); + var action = () => sut.GetRequiredService(); - _ = action.ShouldThrow("Some services are not able to be constructed (Error while validating the service descriptor"); + action.ShouldThrow("Some services are not able to be constructed (Error while validating the service descriptor"); } [Fact(DisplayName = "Does not validate all dependencies can be created when the first service is requested, if ServiceProviderOptions.ValidateOnBuild is false")] @@ -243,31 +244,31 @@ public void Test036() ValidateOnBuild = false, ValidateScopes = true }; - _ = sut.AddSingleton(); - _ = sut.AddSingleton(); + sut.AddSingleton(); + sut.AddSingleton(); var result = sut.GetRequiredService(); - _ = result.ShouldNotBeNull(); + result.ShouldNotBeNull(); } [Fact(DisplayName = "Does not validate all dependencies can be created when the first service is requested, if no ServiceProviderOptions is provided (backwards compatibility)")] public void Test037() { using var sut = new TestServiceProvider(); - _ = sut.AddSingleton(); - _ = sut.AddSingleton(); + sut.AddSingleton(); + sut.AddSingleton(); var result = sut.GetRequiredService(); - _ = result.ShouldNotBeNull(); + result.ShouldNotBeNull(); } [Fact(DisplayName = "Test custom service provider factory")] public void Test038() { using var sut = new TestServiceProvider(); - _ = sut.AddSingleton(); + sut.AddSingleton(); var dummyServiceProviderFactory = new DummyServiceProviderFactory(); @@ -275,10 +276,10 @@ public void Test038() var result = sut.GetRequiredService(); - _ = result.ShouldNotBeNull(); + result.ShouldNotBeNull(); - _ = dummyServiceProviderFactory.TestContainerBuilder.ShouldNotBeNull(); - _ = dummyServiceProviderFactory.TestContainerBuilder.TestServiceProvider.ShouldNotBeNull(); + dummyServiceProviderFactory.TestContainerBuilder.ShouldNotBeNull(); + dummyServiceProviderFactory.TestContainerBuilder.TestServiceProvider.ShouldNotBeNull(); dummyServiceProviderFactory.TestContainerBuilder.TestServiceProvider.ResolvedTestServices.ShouldContain(result); dummyServiceProviderFactory.TestContainerBuilder.TestServiceProvider.ResolvedTestServices.Count.ShouldBe(1); } @@ -287,7 +288,7 @@ public void Test038() public void Test039() { using var sut = new TestServiceProvider(); - _ = sut.AddSingleton(); + sut.AddSingleton(); DummyServiceProvider dummyServiceProvider = null; @@ -295,9 +296,9 @@ public void Test039() var result = sut.GetRequiredService(); - _ = result.ShouldNotBeNull(); + result.ShouldNotBeNull(); - _ = dummyServiceProvider.ShouldNotBeNull(); + dummyServiceProvider.ShouldNotBeNull(); dummyServiceProvider.ResolvedTestServices.ShouldContain(result); dummyServiceProvider.ResolvedTestServices.Count.ShouldBe(1); } @@ -334,7 +335,10 @@ private sealed class DisposableService : IDisposable { public bool IsDisposed { get; private set; } - public void Dispose() => IsDisposed = true; + public void Dispose() + { + IsDisposed = true; + } } private sealed class DummyServiceProvider : IServiceProvider, IServiceScopeFactory, IServiceScope @@ -349,9 +353,7 @@ public DummyServiceProvider(IServiceCollection serviceDescriptors) public object? GetService(Type serviceType) { if (serviceType == typeof(IServiceScope) || serviceType == typeof(IServiceScopeFactory)) - { return this; - } var result = Activator.CreateInstance(serviceDescriptors.Single(x => x.ServiceType == serviceType).ImplementationType); ResolvedTestServices.Add(result); From 8d6d91b9179653cea30a6f66eec654af59013f9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20B=C3=BClow?= Date: Tue, 10 Oct 2023 21:18:50 +0200 Subject: [PATCH 04/13] Update CHANGELOG.md Co-authored-by: Egil Hansen --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 14b3fa3b2..7435f99b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ All notable changes to **bUnit** will be documented in this file. The project ad ### Added -- Support for custom service provider factories (IServiceProviderFactory). This enables the use of Autofac and other frameworks for dependency injection like on real world ASP.NET Core / Blazor projects. +- Support for custom service provider factories (`IServiceProviderFactory`). This enables the use of Autofac and other frameworks for dependency injection like on real-world ASP.NET Core / Blazor projects. By [@inf9144](https://github.com/inf9144). ## [1.23.9] - 2023-09-06 From c0c2682dc6e843dcc619259849b05f620d4f050f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20B=C3=BClow?= Date: Wed, 11 Oct 2023 09:38:56 +0200 Subject: [PATCH 05/13] Allow custom service providers like autofac Adding generic notnull constraint to UseServiceProviderFactory --- src/bunit.core/TestServiceProvider.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bunit.core/TestServiceProvider.cs b/src/bunit.core/TestServiceProvider.cs index f427014b8..aa88cade1 100644 --- a/src/bunit.core/TestServiceProvider.cs +++ b/src/bunit.core/TestServiceProvider.cs @@ -69,7 +69,7 @@ private TestServiceProvider(IServiceCollection initialServiceCollection, bool in /// Use a custom service provider factory for creating the underlying IServiceProvider. /// /// custom service provider factory - public void UseServiceProviderFactory(Func? serviceProviderFactory) + public void UseServiceProviderFactory(Func serviceProviderFactory) { if (serviceProviderFactory is null) { @@ -91,7 +91,7 @@ IServiceProvider BuildServiceProvider() /// /// custom service provider factory /// builder configuration action - public void UseServiceProviderFactory(IServiceProviderFactory serviceProviderFactory, Action? configure = null) + public void UseServiceProviderFactory(IServiceProviderFactory serviceProviderFactory, Action? configure = null) where TContainerBuilder : notnull { if (serviceProviderFactory is null) { From d37705b7d748eda0412419340aac90a9fbe437f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20B=C3=BClow?= Date: Wed, 11 Oct 2023 09:54:21 +0200 Subject: [PATCH 06/13] Allow custom service providers like autofac InitializeProvider public => private --- src/bunit.core/TestServiceProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bunit.core/TestServiceProvider.cs b/src/bunit.core/TestServiceProvider.cs index aa88cade1..c6bb204c7 100644 --- a/src/bunit.core/TestServiceProvider.cs +++ b/src/bunit.core/TestServiceProvider.cs @@ -117,7 +117,7 @@ IServiceProvider BuildServiceProvider() #if !NETSTANDARD2_1 [MemberNotNull(nameof(serviceProvider))] #endif - public void InitializeProvider() + private void InitializeProvider() { CheckInitializedAndThrow(); From 70db36da3a9d420e6880ed0e6445b97340991eca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20B=C3=BClow?= Date: Wed, 11 Oct 2023 10:55:44 +0200 Subject: [PATCH 07/13] Allow custom service providers like autofac Documentation --- .../xunit/CustomServiceProviderFactory.cs | 46 +++++++++++++++++++ .../CustomServiceProviderFactoryUsage.cs | 27 +++++++++++ .../inject-services-into-components.md | 18 ++++++++ 3 files changed, 91 insertions(+) create mode 100644 docs/samples/tests/xunit/CustomServiceProviderFactory.cs create mode 100644 docs/samples/tests/xunit/CustomServiceProviderFactoryUsage.cs diff --git a/docs/samples/tests/xunit/CustomServiceProviderFactory.cs b/docs/samples/tests/xunit/CustomServiceProviderFactory.cs new file mode 100644 index 000000000..545e6a97d --- /dev/null +++ b/docs/samples/tests/xunit/CustomServiceProviderFactory.cs @@ -0,0 +1,46 @@ +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Bunit.Docs.Samples; + +public sealed class CustomServiceProvider : IServiceProvider, IServiceScopeFactory, IServiceScope { + private readonly IServiceProvider _serviceProvider; + + public CustomServiceProvider(IServiceCollection serviceDescriptors) + => _serviceProvider = serviceDescriptors.BuildServiceProvider(); + + public object GetService(Type serviceType) { + if (serviceType == typeof(IServiceScope) || serviceType == typeof(IServiceScopeFactory)) + return this; + + if (serviceType == typeof(DummyService)) + return new DummyService(); + + return _serviceProvider.GetService(serviceType); + } + + void IDisposable.Dispose() { } + public IServiceScope CreateScope() => this; + IServiceProvider IServiceScope.ServiceProvider => this; + +} + +public sealed class CustomServiceProviderFactoryContainerBuilder { + private readonly IServiceCollection _serviceDescriptors; + + public CustomServiceProviderFactoryContainerBuilder(IServiceCollection serviceDescriptors) + => this._serviceDescriptors = serviceDescriptors; + + public IServiceProvider Build() + => new CustomServiceProvider(_serviceDescriptors); +} + +public sealed class CustomServiceProviderFactory : IServiceProviderFactory { + public CustomServiceProviderFactoryContainerBuilder CreateBuilder(IServiceCollection services) + => new CustomServiceProviderFactoryContainerBuilder(services); + + public IServiceProvider CreateServiceProvider(CustomServiceProviderFactoryContainerBuilder containerBuilder) + => containerBuilder.Build(); +} \ No newline at end of file diff --git a/docs/samples/tests/xunit/CustomServiceProviderFactoryUsage.cs b/docs/samples/tests/xunit/CustomServiceProviderFactoryUsage.cs new file mode 100644 index 000000000..096a26be1 --- /dev/null +++ b/docs/samples/tests/xunit/CustomServiceProviderFactoryUsage.cs @@ -0,0 +1,27 @@ +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; +using System.Text; +using Xunit; + +namespace Bunit.Docs.Samples; +public class CustomServiceProviderFactoryUsage : TestContext { + [Fact] + public void CustomServiceProviderViaFactoryReturns() { + Services.UseServiceProviderFactory(new CustomServiceProviderFactory()); + + var dummyService = Services.GetService(); + + Assert.NotNull(dummyService); + } + + [Fact] + public void CustomServiceProviderViaDelegateReturns() { + Services.UseServiceProviderFactory(x => new CustomServiceProvider(x)); + + var dummyService = Services.GetService(); + + Assert.NotNull(dummyService); + } + +} diff --git a/docs/site/docs/providing-input/inject-services-into-components.md b/docs/site/docs/providing-input/inject-services-into-components.md index b3becb15e..cccc65b9a 100644 --- a/docs/site/docs/providing-input/inject-services-into-components.md +++ b/docs/site/docs/providing-input/inject-services-into-components.md @@ -51,6 +51,24 @@ Here is a test where the fallback service provider is used: In this example, the `DummyService` is provided by the fallback service provider, since it is not registered in the default service provider. +## Using a custom IServiceProvider implementation +A custom service provider factory can be registered with the built-in `TestServiceProvider`. It is used to create the underlying IServiceProvider. This enables a few interesting use cases, such as using an alternative IoC container (which should implement the `IServiceProvider` interface). This approach can be useful if the fallback service provider is not an option. For example, if you have dependencies in the fallback container, that rely on dependencies which are in the main container and vice versa. + +### Registering a custom service provider factory +The examples contain dummy implementations of `IServiceProvider` and `IServiceProviderFactory`. Normally those implementations are supplied by the creator of your custom dependency injection solution. This dummy implementations are not intended to use as is. + +This is an example of how to implement and use a dummy custom service provider factory. + +[!code-csharp[](../../../samples/tests/xunit/CustomServiceProviderFactory.cs?start=8&end=46)] + +Here is a test where the custom service provider factory is used: + +[!code-csharp[](../../../samples/tests/xunit/CustomServiceProviderFactoryUsage.cs?start=11&end=15)] + +Here is a test where the custom service provider is used via delegate: + +[!code-csharp[](../../../samples/tests/xunit/CustomServiceProviderFactoryUsage.cs?start=20&end=24)] + ## Further reading A closely related topic is mocking. To learn more about mocking in bUnit, go to the page. From 7cfff817429bfdd2d84d7f3d63fe854744b9ffdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20B=C3=BClow?= Date: Thu, 12 Oct 2023 08:46:46 +0200 Subject: [PATCH 08/13] Apply suggestions from code review Co-authored-by: Egil Hansen --- src/bunit.core/TestServiceProvider.cs | 26 +++++++------------ .../TestServiceProviderTest.cs | 6 ----- 2 files changed, 10 insertions(+), 22 deletions(-) diff --git a/src/bunit.core/TestServiceProvider.cs b/src/bunit.core/TestServiceProvider.cs index c6bb204c7..d3a60104b 100644 --- a/src/bunit.core/TestServiceProvider.cs +++ b/src/bunit.core/TestServiceProvider.cs @@ -15,7 +15,7 @@ public sealed class TestServiceProvider : IServiceProvider, IServiceCollection, private IServiceProvider? serviceProvider; private IServiceProvider? fallbackServiceProvider; private ServiceProviderOptions options = DefaultServiceProviderOptions; - private Func? buildServiceProviderDelegate; + private Func serviceProviderFactory = () => serviceCollection.BuildServiceProvider(); /// /// Gets a value indicating whether this has been initialized, and @@ -76,10 +76,7 @@ public void UseServiceProviderFactory(Func throw new ArgumentNullException(nameof(serviceProviderFactory)); } - buildServiceProviderDelegate = BuildServiceProvider; - - IServiceProvider BuildServiceProvider() - => serviceProviderFactory(serviceCollection); + this.serviceProviderFactory = () => serviceProviderFactory(serviceCollection); } /// @@ -98,14 +95,14 @@ public void UseServiceProviderFactory(IServiceProviderFactory throw new ArgumentNullException(nameof(serviceProviderFactory)); } - buildServiceProviderDelegate = BuildServiceProvider; - IServiceProvider BuildServiceProvider() - { - var containerBuilder = serviceProviderFactory.CreateBuilder(serviceCollection); - configure?.Invoke(containerBuilder); - return serviceProviderFactory.CreateServiceProvider(containerBuilder); - } + UseServiceProviderFactory( + serviceCollection => + { + var containerBuilder = serviceProviderFactory.CreateBuilder(serviceCollection); + configure?.Invoke(containerBuilder); + return serviceProviderFactory.CreateServiceProvider(containerBuilder); + }); } /// @@ -122,12 +119,9 @@ private void InitializeProvider() CheckInitializedAndThrow(); serviceCollection.AddSingleton(this); - rootServiceProvider = (buildServiceProviderDelegate ?? BuildServiceProvider)(); + rootServiceProvider = serviceProviderFactory.Invoke(); serviceScope = rootServiceProvider.CreateScope(); serviceProvider = serviceScope.ServiceProvider; - - IServiceProvider BuildServiceProvider() - => serviceCollection.BuildServiceProvider(options); } /// diff --git a/tests/bunit.core.tests/TestServiceProviderTest.cs b/tests/bunit.core.tests/TestServiceProviderTest.cs index 05ee9f4c1..92b5ba293 100644 --- a/tests/bunit.core.tests/TestServiceProviderTest.cs +++ b/tests/bunit.core.tests/TestServiceProviderTest.cs @@ -269,15 +269,12 @@ public void Test038() { using var sut = new TestServiceProvider(); sut.AddSingleton(); - var dummyServiceProviderFactory = new DummyServiceProviderFactory(); - sut.UseServiceProviderFactory(dummyServiceProviderFactory); var result = sut.GetRequiredService(); result.ShouldNotBeNull(); - dummyServiceProviderFactory.TestContainerBuilder.ShouldNotBeNull(); dummyServiceProviderFactory.TestContainerBuilder.TestServiceProvider.ShouldNotBeNull(); dummyServiceProviderFactory.TestContainerBuilder.TestServiceProvider.ResolvedTestServices.ShouldContain(result); @@ -289,15 +286,12 @@ public void Test039() { using var sut = new TestServiceProvider(); sut.AddSingleton(); - DummyServiceProvider dummyServiceProvider = null; - sut.UseServiceProviderFactory(x => dummyServiceProvider = new DummyServiceProvider(x)); var result = sut.GetRequiredService(); result.ShouldNotBeNull(); - dummyServiceProvider.ShouldNotBeNull(); dummyServiceProvider.ResolvedTestServices.ShouldContain(result); dummyServiceProvider.ResolvedTestServices.Count.ShouldBe(1); From 5277f72221ce77fd7d0e51289f2b2ea83da624f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20B=C3=BClow?= Date: Thu, 12 Oct 2023 08:54:59 +0200 Subject: [PATCH 09/13] Allow custom service providers like autofac Fix code from code review applied changes --- src/bunit.core/TestServiceProvider.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/bunit.core/TestServiceProvider.cs b/src/bunit.core/TestServiceProvider.cs index d3a60104b..fc0439b48 100644 --- a/src/bunit.core/TestServiceProvider.cs +++ b/src/bunit.core/TestServiceProvider.cs @@ -15,7 +15,7 @@ public sealed class TestServiceProvider : IServiceProvider, IServiceCollection, private IServiceProvider? serviceProvider; private IServiceProvider? fallbackServiceProvider; private ServiceProviderOptions options = DefaultServiceProviderOptions; - private Func serviceProviderFactory = () => serviceCollection.BuildServiceProvider(); + private Func serviceProviderFactory; /// /// Gets a value indicating whether this has been initialized, and @@ -61,6 +61,8 @@ public TestServiceProvider(IServiceCollection? initialServiceCollection = null) private TestServiceProvider(IServiceCollection initialServiceCollection, bool initializeProvider) { serviceCollection = initialServiceCollection; + serviceProviderFactory = () => serviceCollection.BuildServiceProvider(); + if (initializeProvider) InitializeProvider(); } From 797fa53d081f1956c9d1ffaea2706c3d37bd9ed6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20B=C3=BClow?= Date: Thu, 12 Oct 2023 09:26:41 +0200 Subject: [PATCH 10/13] Allow custom service providers like autofac Fix code from code review applied changes --- src/bunit.core/TestServiceProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bunit.core/TestServiceProvider.cs b/src/bunit.core/TestServiceProvider.cs index fc0439b48..ccbb228ae 100644 --- a/src/bunit.core/TestServiceProvider.cs +++ b/src/bunit.core/TestServiceProvider.cs @@ -61,7 +61,7 @@ public TestServiceProvider(IServiceCollection? initialServiceCollection = null) private TestServiceProvider(IServiceCollection initialServiceCollection, bool initializeProvider) { serviceCollection = initialServiceCollection; - serviceProviderFactory = () => serviceCollection.BuildServiceProvider(); + serviceProviderFactory = () => serviceCollection.BuildServiceProvider(Options); if (initializeProvider) InitializeProvider(); From 43fe591579f9e2a477ce030787bcecae3340873a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20B=C3=BClow?= Date: Thu, 12 Oct 2023 09:58:41 +0200 Subject: [PATCH 11/13] Allow custom service providers like autofac Documentation --- .../CustomServiceProviderFactoryUsage.cs | 55 +++++++++++++++++++ .../xunit/bunit.docs.xunit.samples.csproj | 2 + .../inject-services-into-components.md | 17 +++++- 3 files changed, 71 insertions(+), 3 deletions(-) diff --git a/docs/samples/tests/xunit/CustomServiceProviderFactoryUsage.cs b/docs/samples/tests/xunit/CustomServiceProviderFactoryUsage.cs index 096a26be1..9bcbeb18e 100644 --- a/docs/samples/tests/xunit/CustomServiceProviderFactoryUsage.cs +++ b/docs/samples/tests/xunit/CustomServiceProviderFactoryUsage.cs @@ -1,3 +1,5 @@ +using Autofac; +using Autofac.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.Generic; @@ -24,4 +26,57 @@ public void CustomServiceProviderViaDelegateReturns() { Assert.NotNull(dummyService); } + [Fact] + public void AutofacServiceProviderViaFactoryReturns() { + void ConfigureContainer(ContainerBuilder containerBuilder) { + containerBuilder + .RegisterType() + .AsSelf(); + } + + Services.UseServiceProviderFactory(new AutofacServiceProviderFactory(ConfigureContainer)); + + //get a service which was installed in the Autofac ContainerBuilder + + var dummyService = Services.GetService(); + + Assert.NotNull(dummyService); + + //get a service which was installed in the bUnit ServiceCollection + + var testContextBase = Services.GetService(); + + Assert.NotNull(testContextBase); + Assert.Equal(this, testContextBase); + } + + [Fact] + public void AutofacServiceProviderViaDelegateReturns() { + ILifetimeScope ConfigureContainer(IServiceCollection services) { + var containerBuilder = new ContainerBuilder(); + + containerBuilder + .RegisterType() + .AsSelf(); + + containerBuilder.Populate(services); + + return containerBuilder.Build(); + } + + Services.UseServiceProviderFactory(x => new AutofacServiceProvider(ConfigureContainer(x))); + + //get a service which was installed in the Autofac ContainerBuilder + + var dummyService = Services.GetService(); + + Assert.NotNull(dummyService); + + //get a service which was installed in the bUnit ServiceCollection + + var testContextBase = Services.GetService(); + + Assert.NotNull(testContextBase); + Assert.Equal(this, testContextBase); + } } diff --git a/docs/samples/tests/xunit/bunit.docs.xunit.samples.csproj b/docs/samples/tests/xunit/bunit.docs.xunit.samples.csproj index 03f4838e3..d77512f72 100644 --- a/docs/samples/tests/xunit/bunit.docs.xunit.samples.csproj +++ b/docs/samples/tests/xunit/bunit.docs.xunit.samples.csproj @@ -5,6 +5,8 @@ + + diff --git a/docs/site/docs/providing-input/inject-services-into-components.md b/docs/site/docs/providing-input/inject-services-into-components.md index cccc65b9a..639142b55 100644 --- a/docs/site/docs/providing-input/inject-services-into-components.md +++ b/docs/site/docs/providing-input/inject-services-into-components.md @@ -54,8 +54,19 @@ In this example, the `DummyService` is provided by the fallback service provider ## Using a custom IServiceProvider implementation A custom service provider factory can be registered with the built-in `TestServiceProvider`. It is used to create the underlying IServiceProvider. This enables a few interesting use cases, such as using an alternative IoC container (which should implement the `IServiceProvider` interface). This approach can be useful if the fallback service provider is not an option. For example, if you have dependencies in the fallback container, that rely on dependencies which are in the main container and vice versa. +### Registering Autofac service provider factory +The example makes use of `AutofacServiceProviderFactory` and `AutofacServiceProvider` from the package `Autofac.Extensions.DependencyInjection` and shows how to use an Autofac dependency container with bUnit. + +Here is a test where the Autofac service provider factory is used: + +[!code-csharp[](../../../samples/tests/xunit/CustomServiceProviderFactoryUsage.cs?start=31&end=50)] + +Here is a test where the Autofac service provider is used via delegate: + +[!code-csharp[](../../../samples/tests/xunit/CustomServiceProviderFactoryUsage.cs?start=55&end=80)] + ### Registering a custom service provider factory -The examples contain dummy implementations of `IServiceProvider` and `IServiceProviderFactory`. Normally those implementations are supplied by the creator of your custom dependency injection solution. This dummy implementations are not intended to use as is. +The examples contain dummy implementations of `IServiceProvider` and `IServiceProviderFactory`. Normally those implementations are supplied by the creator of your custom dependency injection solution (e.g. Autofac example above). This dummy implementations are not intended to use as is. This is an example of how to implement and use a dummy custom service provider factory. @@ -63,11 +74,11 @@ This is an example of how to implement and use a dummy custom service provider f Here is a test where the custom service provider factory is used: -[!code-csharp[](../../../samples/tests/xunit/CustomServiceProviderFactoryUsage.cs?start=11&end=15)] +[!code-csharp[](../../../samples/tests/xunit/CustomServiceProviderFactoryUsage.cs?start=13&end=17)] Here is a test where the custom service provider is used via delegate: -[!code-csharp[](../../../samples/tests/xunit/CustomServiceProviderFactoryUsage.cs?start=20&end=24)] +[!code-csharp[](../../../samples/tests/xunit/CustomServiceProviderFactoryUsage.cs?start=22&end=26)] ## Further reading From ba846cfeda944435e19e194d4ab9088f33e5396e Mon Sep 17 00:00:00 2001 From: Egil Hansen Date: Thu, 12 Oct 2023 21:09:16 +0000 Subject: [PATCH 12/13] Update src/bunit.core/TestServiceProvider.cs --- src/bunit.core/TestServiceProvider.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/bunit.core/TestServiceProvider.cs b/src/bunit.core/TestServiceProvider.cs index ccbb228ae..9ba8d0699 100644 --- a/src/bunit.core/TestServiceProvider.cs +++ b/src/bunit.core/TestServiceProvider.cs @@ -97,7 +97,6 @@ public void UseServiceProviderFactory(IServiceProviderFactory throw new ArgumentNullException(nameof(serviceProviderFactory)); } - UseServiceProviderFactory( serviceCollection => { From 8eef76197e505192287a07f3a98f9e3dc37350cf Mon Sep 17 00:00:00 2001 From: Egil Hansen Date: Thu, 12 Oct 2023 21:11:35 +0000 Subject: [PATCH 13/13] Update docs/samples/tests/xunit/CustomServiceProviderFactoryUsage.cs --- .../CustomServiceProviderFactoryUsage.cs | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/docs/samples/tests/xunit/CustomServiceProviderFactoryUsage.cs b/docs/samples/tests/xunit/CustomServiceProviderFactoryUsage.cs index 9bcbeb18e..df4e5800d 100644 --- a/docs/samples/tests/xunit/CustomServiceProviderFactoryUsage.cs +++ b/docs/samples/tests/xunit/CustomServiceProviderFactoryUsage.cs @@ -7,9 +7,11 @@ using Xunit; namespace Bunit.Docs.Samples; -public class CustomServiceProviderFactoryUsage : TestContext { +public class CustomServiceProviderFactoryUsage : TestContext +{ [Fact] - public void CustomServiceProviderViaFactoryReturns() { + public void CustomServiceProviderViaFactoryReturns() + { Services.UseServiceProviderFactory(new CustomServiceProviderFactory()); var dummyService = Services.GetService(); @@ -18,7 +20,8 @@ public void CustomServiceProviderViaFactoryReturns() { } [Fact] - public void CustomServiceProviderViaDelegateReturns() { + public void CustomServiceProviderViaDelegateReturns() + { Services.UseServiceProviderFactory(x => new CustomServiceProvider(x)); var dummyService = Services.GetService(); @@ -27,8 +30,10 @@ public void CustomServiceProviderViaDelegateReturns() { } [Fact] - public void AutofacServiceProviderViaFactoryReturns() { - void ConfigureContainer(ContainerBuilder containerBuilder) { + public void AutofacServiceProviderViaFactoryReturns() + { + void ConfigureContainer(ContainerBuilder containerBuilder) + { containerBuilder .RegisterType() .AsSelf(); @@ -51,8 +56,10 @@ void ConfigureContainer(ContainerBuilder containerBuilder) { } [Fact] - public void AutofacServiceProviderViaDelegateReturns() { - ILifetimeScope ConfigureContainer(IServiceCollection services) { + public void AutofacServiceProviderViaDelegateReturns() + { + ILifetimeScope ConfigureContainer(IServiceCollection services) + { var containerBuilder = new ContainerBuilder(); containerBuilder