Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

310 fallback service provider #315

Merged
merged 29 commits into from
Feb 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
b61b4ab
updated telerik ad
egil Jan 20, 2021
dcb235e
Update emulating-ijsruntime.md
egil Jan 30, 2021
a779c36
Added fallback Serviceprovider and test for it.
thopdev Feb 3, 2021
c787168
added docs for the fallback service provider
thopdev Feb 4, 2021
21bfc7e
small improvement
thopdev Feb 4, 2021
bb5edc0
small doc improvments
thopdev Feb 4, 2021
5d0e5d7
Method overloads should be grouped together
thopdev Feb 4, 2021
05f498d
Merge branch 'dev' into 310-FallbackServiceProvider
thopdev Feb 4, 2021
941e2bb
changes for merge
thopdev Feb 4, 2021
8202f6e
unwanted change and doc improvement
thopdev Feb 4, 2021
555fb46
removed unwanted change
thopdev Feb 4, 2021
0cb91b6
Update tests/bunit.core.tests/TestServiceProviderTest.cs
thopdev Feb 5, 2021
4af434d
Update tests/bunit.core.tests/TestServiceProviderTest.cs
thopdev Feb 5, 2021
4a27e3a
Update docs/site/docs/providing-input/inject-services-into-components.md
thopdev Feb 5, 2021
966a014
changed to internal
thopdev Feb 5, 2021
5d92707
Merge branch '310-FallbackServiceProvider' of https://github.com/thop…
thopdev Feb 5, 2021
74d133c
ArgumentNullException on null
thopdev Feb 5, 2021
a964a36
Adding more tests
thopdev Feb 5, 2021
a3efb39
Improved docs for service provider
thopdev Feb 5, 2021
e8254d8
more doc
thopdev Feb 5, 2021
911e13f
whitespace
thopdev Feb 5, 2021
e9f447a
Tweaks to docs
egil Feb 6, 2021
7aa4d7a
Update tests/bunit.core.tests/TestServiceProviderTest.cs
thopdev Feb 7, 2021
7b9e189
Update tests/bunit.core.tests/TestServiceProviderTest.cs
thopdev Feb 7, 2021
f05a63a
Update src/bunit.core/TestServiceProvider.cs
thopdev Feb 7, 2021
a165854
Update src/bunit.core/TestServiceProvider.cs
thopdev Feb 7, 2021
b2d00b0
fixed return statement
thopdev Feb 7, 2021
076954a
Update docs/site/docs/providing-input/inject-services-into-components.md
thopdev Feb 8, 2021
cad598e
Updated changelog
egil Feb 8, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ All notable changes to **bUnit** will be documented in this file. The project ad
### Added
List of new features.

- Added the ability to pass a "fallback `IServiceProvider`" to the `TestServiceProvider`, available through the `Services` property on a `TestContext`. The fallback service provider enables a few interesting scenarios, such as using an alternative IoC container, or automatically generating mocks of services components under test depend on. See the [Injecting Services into Components Under Test page](https://bunit.egilhansen.com/docs/providing-input/inject-services-into-components) for more details on this feature. By [@thopdev](https://github.com/thopdev) in [#310](https://github.com/egil/bUnit/issues/310).

### Changed
List of changes in existing functionality.

Expand Down
19 changes: 19 additions & 0 deletions docs/samples/tests/xunit/FallBackServiceProviderUsage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using Xunit;
using Bunit;

namespace Bunit.Docs.Samples
{
public class FallBackServiceProviderUssageExample
{
[Fact]
public void FallBackServiceProviderReturns()
{
using var ctx = new TestContext();
ctx.Services.AddFallbackServiceProvider(new FallbackServiceProvider());

var dummyService = ctx.Services.GetService<DummyService>();

Assert.NotNull(dummyService);
}
}
}
14 changes: 14 additions & 0 deletions docs/samples/tests/xunit/FallbackServiceProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System;

namespace Bunit.Docs.Samples
{
public class FallbackServiceProvider : IServiceProvider
{
public object GetService(Type serviceType)
{
return new DummyService();
}
}

public class DummyService { }
}
25 changes: 25 additions & 0 deletions docs/site/docs/providing-input/inject-services-into-components.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,31 @@ The following example shows how to do this with `<SnapshotTest>` tests:
> [!NOTE]
> The `AddSingleton()` method is only available on the `Services` collection if you import the `Microsoft.Extensions.DependencyInjection` type.

## Fallback Service Provider

A fallback service provider can be registered with the built-in `TestServiceProvider`. This enables a few interesting use cases, such as using an alternative IoC container (which should implement the `IServiceProvider` interface), or automatically creating mock services for your Blazor components. The latter can be achieved by using a combination of [AutoFixture](https://github.com/AutoFixture/AutoFixture) and your favorite mocking framework, e.g. Moq, NSubsitute, or Telerik JustMock.

### When is the Fallback Service Provider Used?

The logic inside the `TestServiceProvider` for using the fallback service provider is as follows:

1. Try resolving the requested service from the standard service provider in bUnit.
2. If that fails, try resolving from a fallback service proider, if one exists.

In other words, the fallback service provider will always be tried after the default service provider has had a chance to fulfill a request for a service.

### Registering a Fallback Service Provider

This is an example of how to implement and use a fallback service provider:

[!code-csharp[](../../../samples/tests/xunit/FallbackServiceProvider.cs?start=5&end=13)]

Here is a test where the fallback service provider is used:

[!code-csharp[](../../../samples/tests/xunit/FallBackServiceProviderUsage.cs?start=11&end=16)]

In this example, the `DummyService` is provided by the fallback service provider, since it is not reigsted in the default service provider.

## Further Reading

A closely related topic is mocking. To learn more about mocking in bUnit, go to the <xref:test-doubles> page.
2 changes: 0 additions & 2 deletions docs/site/docs/test-doubles/emulating-ijsruntime.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,5 +155,3 @@ ctx.JSInterop.VerifyFocusAsyncInvoke() // Verifies that a FocusAsync call has ha
## Support for `IJSInProcessRuntime` and `IJSUnmarshalledRuntime`

bUnit's `IJSRuntime` supports being cast to the `IJSInProcessRuntime` and `IJSUnmarshalledRuntime` types, just like Blazors `IJSRuntime`.

To set up a handler for a `Invoke` and `InvokeUnmarshalled` call, just use the regular `Setup` and `SetupVoid` methods on bUnit's JSInterop.
14 changes: 8 additions & 6 deletions docs/site/sponsors/telerik-ad-github.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
28 changes: 25 additions & 3 deletions src/bunit.core/TestServiceProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;

namespace Bunit
Expand All @@ -15,6 +14,7 @@ public sealed class TestServiceProvider : IServiceProvider, IServiceCollection,
{
private readonly IServiceCollection serviceCollection;
private ServiceProvider? serviceProvider;
private IServiceProvider? fallbackServiceProvider;

/// <summary>
/// Gets a value indicating whether this <see cref="TestServiceProvider"/> has been initialized, and
Expand Down Expand Up @@ -55,20 +55,42 @@ private TestServiceProvider(IServiceCollection initialServiceCollection, bool in
serviceProvider = serviceCollection.BuildServiceProvider();
}

/// <summary>
/// Add a fall back service provider that provides services when the default returns null
/// </summary>
/// <param name="serviceProvider">The fallback service provider</param>
public void AddFallbackServiceProvider(IServiceProvider serviceProvider)
=> fallbackServiceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));

/// <summary>
/// Get service of type T from the test provider.
/// </summary>
/// <typeparam name="TService">The type of service object to get.</typeparam>
/// <returns>A service object of type T or null if there is no such service.</returns>
public TService GetService<TService>() => (TService)GetService(typeof(TService));
public TService? GetService<TService>() => (TService?)GetService(typeof(TService))!;

#if NETSTANDARD2_1
/// <inheritdoc/>
[return: MaybeNull]
public object GetService(Type serviceType)
=> GetServiceInternal(serviceType);
#else
/// <inheritdoc/>
public object? GetService(Type serviceType)
=> GetServiceInternal(serviceType);
#endif

private object? GetServiceInternal(Type serviceType)
{
if (serviceProvider is null)
serviceProvider = serviceCollection.BuildServiceProvider();

return serviceProvider.GetService(serviceType);
var result = serviceProvider.GetService(serviceType);

if (result is null && fallbackServiceProvider is not null)
result = fallbackServiceProvider.GetService(serviceType);

return result;
}

/// <inheritdoc/>
Expand Down
67 changes: 67 additions & 0 deletions tests/bunit.core.tests/TestServiceProviderTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,16 @@ private class AnotherDummyService { }

private class OneMoreDummyService { }

private class FallbackServiceProvider : IServiceProvider
{
public object GetService(Type serviceType) => new DummyService();
}

private class AnotherFallbackServiceProvider : IServiceProvider
{
public object GetService(Type serviceType) => new AnotherDummyService();
}

[Fact(DisplayName = "Provider initialized without a service collection has zero services by default")]
public void Test001()
{
Expand Down Expand Up @@ -122,5 +132,62 @@ public void Test020()

actual.ShouldBe(expected);
}

[Fact(DisplayName = "No registered service returns null")]
public void Test021()
{
using var sut = new TestServiceProvider();

var result = sut.GetService(typeof(DummyService));
thopdev marked this conversation as resolved.
Show resolved Hide resolved

Assert.Null(result);
}

[Fact(DisplayName= "Registered fallback service provider returns value")]
public void Test022()
{
using var sut = new TestServiceProvider();
sut.AddFallbackServiceProvider(new FallbackServiceProvider());

var result = sut.GetService(typeof(object));
thopdev marked this conversation as resolved.
Show resolved Hide resolved

Assert.NotNull(result);
Assert.IsType<DummyService>(result);
}

[Fact(DisplayName = "Register fallback service with null value")]
public void Test023()
{
using var sut = new TestServiceProvider();
Assert.Throws<ArgumentNullException>(() => sut.AddFallbackServiceProvider(null!));
}

[Fact(DisplayName = "Service provider returns value before fallback service provider")]
public void Test024()
{
const string exceptionStringResult = "exceptionStringResult";

using var sut = new TestServiceProvider();
sut.AddSingleton<string>(exceptionStringResult);
sut.AddFallbackServiceProvider(new FallbackServiceProvider());

var stringResult = sut.GetService(typeof(string));
Assert.Equal(exceptionStringResult, stringResult);

var fallbackResult = sut.GetService(typeof(DummyService));
Assert.IsType<DummyService>(fallbackResult);
}

[Fact(DisplayName = "Latest fallback provider is used")]
public void Test025()
{
using var sut = new TestServiceProvider();
sut.AddFallbackServiceProvider(new FallbackServiceProvider());
sut.AddFallbackServiceProvider(new AnotherFallbackServiceProvider());

var result = sut.GetService(typeof(object));

Assert.IsType<AnotherDummyService>(result);
}
}
}