Closed egil closed 3 years ago
Hi @egil, i was looking into it. And i would like to discuss my suggestion before cleaning up etc.. My suggestion/plan: The TestServiceProvider is available though the TestContext. For getting services the TestServiceProvider has a function:
public object GetService(Type serviceType)
{
if (_serviceProvider is null)
_serviceProvider = _serviceCollection.BuildServiceProvider();
return _serviceProvider.GetService(serviceType);
}
My plan is to alter this function with a Func like this:
public Func<Type, object> CustomerServiceCreationFunc { get; set; }
/// <inheritdoc/>
public object GetService(Type serviceType)
{
var result = CustomerServiceCreationFunc.Invoke(serviceType);
if (result != null)
return result;
if (_serviceProvider is null)
_serviceProvider = _serviceCollection.BuildServiceProvider();
return _serviceProvider.GetService(serviceType);
}
Was wondering what your opinion on it is?
Thanks @thopdev. I'm not at home right now, but will respond in detail later.
We are not too far apart in our thinking of this @thopdev, however, I have a slight change to the order of how things happens.
To TestServiceProvider
, I want to add the following (naming tentative):
AddFallbackServiceProvider(IServiceProvider fallbackServiceProvider);
Then, the GetServices
should look like this:
public object GetService(Type serviceType)
{
if (_serviceProvider is null)
_serviceProvider = _serviceCollection.BuildServiceProvider();
var result = _serviceProvider.GetService(serviceType);
if (result is null && fallbackServiceProvider is not null)
result = fallbackServiceProvider.BuildServiceProvider();
return result;
}
So why not call the fallback first? The idea is that 3rd party component library venders can have an e.g. ctx.SetupTelerik()
method, which might add services, components to the render tree (through the RenderTree property), or configure JSInterop.
If the fallback is called first, then any services already registered that might not need to be mocked, will be, if the fallback is something like Moq or NSubstitute.
What do you think?
Now i think about about the order, your right. The user should always be allowed to override the fallback. And instead of the Func using a other IServiceProvider sounds good.
When i get time i'll start working on a PR. Not sure if i can do it today as i'm streaming tonight. But maybe i get some time before otherwise tomorrow :)
Sounds good (where do you stream?).
This is probably a PR that will require more work with writing documentation than actual code, e.g. an extra section at the bottom of this page: https://bunit.egilhansen.com/docs/providing-input/inject-services-into-components.html
A paragraph or two that explains how the fallback logic works, and an example that shows how to utilize it with a mocking framework would be great. In other words, as short as possible, but long enough that folks get the point of the feature.
Good you say that, i'll also add some documentation. I stream at twitch (twitch.tv/thopdev) from 7pm CET. I'm creating a Blazor todo app with azure func etc.
/// <inheritdoc/>
public object GetService(Type serviceType)
{
if (_serviceProvider is null)
_serviceProvider = _serviceCollection.BuildServiceProvider();
var result = _serviceProvider.GetService(serviceType);
if (result is null && _fallbackServiceProvider is not null)
result = _fallbackServiceProvider.GetService(serviceType);
#pragma warning disable CS8603 // Possible null reference return.
return result;
#pragma warning restore CS8603 // Possible null reference return.
}
result can still be null, i can't make the return type object? so i was wondering if you would know a better solution? @egil
result can still be null, i can't make the return type object? so i was wondering if you would know a better solution? @egil
In NET5 the IServiceProvider
actually defines the return obejct as nullable, so we can use #if NET5_0
to create a variant that does that, and one that doesn't, where we do return result!;
Thansk for the response, oke thanks, never worked with multiple versions before. But ill do that when i get to my pc. For the tests, its that a logic to the numbering or should i just just the latest's + 1?
@egil I'm currently working on the documentation part. I wanted to add some code samples but I'm not completely sure where to place them, as its not really a component but they are also not testing framework specific. What is your preference? Contains of example fallbackserviceprovider and test
@thopdev sounds great. Just a quick response now, more details later if needed:
ideally doc samples are runnable from the samples folder, e.g. as xunit tests. It is pretty complex to get docs working locally, so feel free to just include samples inline in the markdown file.
no hard or fast rule about the amount of unit tests needed for the new code. All code branches should be covered, e.g.:
These are just the ones I could come up with without looking at the code.
https://github.com/egil/bUnit/pull/315 Il also move the tests to xunit and make them work. And add the bad argument, the rest should be done. @egil
Implemented in #315.
To enable automatic mocking of services required by components, e.g. when using AutoFixture in combination with a mocking framework, there should be a way to either replace the
TestServicesProvider
or to have it redirect to a user providedIServiceProvider
when it cannot resolve a service itself.This is inspired by the post on using AutoFixture and Moq to simplify the "arrange" step of a test, described here: https://github.com/egil/bUnit/discussions/307.