bUnit-dev / bUnit

bUnit is a testing library for Blazor components that make tests look, feel, and runs like regular unit tests. bUnit makes it easy to render and control a component under test’s life-cycle, pass parameter and inject services into it, trigger event handlers, and verify the rendered markup from the component using a built-in semantic HTML comparer.
https://bunit.dev
MIT License
1.15k stars 108 forks source link

Deeper integration with mocking frameworks #310

Closed egil closed 3 years ago

egil commented 3 years ago

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 provided IServiceProvider 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.

thopdev commented 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?

egil commented 3 years ago

Thanks @thopdev. I'm not at home right now, but will respond in detail later.

egil commented 3 years ago

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?

thopdev commented 3 years ago

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 :)

egil commented 3 years ago

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.

thopdev commented 3 years ago

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.

thopdev commented 3 years ago
        /// <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.
        } 
thopdev commented 3 years ago

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

egil commented 3 years ago

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!;

thopdev commented 3 years ago

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?

thopdev commented 3 years ago

@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

egil commented 3 years ago

@thopdev sounds great. Just a quick response now, more details later if needed:

These are just the ones I could come up with without looking at the code.

thopdev commented 3 years ago

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

egil commented 3 years ago

Implemented in #315.