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

New LookupOrFallbackDefaultValueProvider base class for custom default value providers #536

Merged
merged 3 commits into from
Nov 28, 2017

Conversation

stakx
Copy link
Contributor

@stakx stakx commented Nov 28, 2017

This adds a new base class LookupOrFallbackDefaultValueProvider, which builds on top of DefaultValueProvider, to Moq's public API.

  • This type becomes the new base class for both EmptyDefaultValueProvider and MockDefaultValueProvider.

  • It has built-in support for task types Task, Task<TResult>, and ValueTask<TResult>.

  • It also provides enough customizability for folks to easily create their own default value providers that work like the empty default value provider. Here's an example of a provider that will produce empty collections for IReadOnlyList<T>:

    sealed class MyEmptyDefaultValueProvider : LookupOrFallbackDefaultValueProvider
    {
        public MyEmptyDefaultValueProvider()
        {
            base.Register(typeof(IReadOnlyList<>), CreateReadOnlyListOf);
        }
    
        private static CreateReadOnlyListOf(Type type, Mock _)
        {
            var listType = typeof(List<>).MakeGenericType(type.GetGenericArguments());
            return Activator.CreateInstance(listType);
        }
    }

For the rationale behind this new type, and the reasoning why it doesn't come preconfigured as an empty default value provider supporting all standard collection types from the .NET BCL, see #173.

Perhaps we can add a customizable or expanded empty default value provider at a later time if there's still widespread demand / need for it.

This abstract base class offers the basic functionality required by
providers akin to the empty default value provider. That is, it allows
setting up different value generation strategies for different types,
and it can fall back to a default generation strategy if a request is
made for which no specific strategy is available.

It also handles task types such that values are automatically wrapped
as completed `Task<TResult>` or `ValueTask<TResult>`.

Both `EmptyDefaultValueProvider` and `MockDefaultValueProvider` are
refactored to derive from this type (which incidentally makes them a
tiny bit faster).
@stakx stakx merged commit 351bab0 into devlooped:develop Nov 28, 2017
@stakx stakx deleted the lookuporfallbackdefaultvalueprovider branch November 29, 2017 08:58
@Serg046
Copy link

Serg046 commented Apr 5, 2018

But why is it sealed?
protected internal sealed override object GetDefaultValue(Type type, Mock mock)

I would like to override it to customize batch of types instead of Register(...) call for each type. Something like this:

protected override object GetDefaultValue(Type type, Mock mock)
{
    var enumerableType = typeof(IEnumerable<>).MakeGenericType(type.GetGenericArguments());
    if (enumerableType.IsAssignableFrom(type))
    {
        var listType = typeof(List<>).MakeGenericType(type.GetGenericArguments());
        return Activator.CreateInstance(listType);
    }
    base.GetDefaultValue(type, mock);
}

@stakx
Copy link
Contributor Author

stakx commented Apr 5, 2018

But why is it sealed?

In order to guarantee a certain behavior with generic and array types.

(And because I believe that composition is generally better for composing behavior than inheritance, so I tend to define methods as either abstract, or sealed... but not virtual.)

If that's not what you want, you can always subclass the base class DefaultValueProvider.

@Serg046
Copy link

Serg046 commented Apr 5, 2018

Yes, DefaultValueProvider is good way but how to get empty values for other types that are not covered my GetDefaultValue implementation? I can get it from LookupOrFallbackDefaultValueProvider's GetDefaultValue method if it is public (by composition) or not sealed (by inheritance). It contains important logic like values for Task etc.

@stakx
Copy link
Contributor Author

stakx commented Apr 5, 2018

Sorry I forgot to say, instead of trying to override GetDefaultValue, you can override GetFallbackDefaultValue which is what'll get invoked when there's no registered factory for a particular type.

Also, you can always use composition with / delegation to another default value provider (e.g. DefaultValueProvider.Empty) to cover those cases that your own provider does not handle itself.

@Serg046
Copy link

Serg046 commented Apr 5, 2018

I cannot use DefaultValueProvider.Empty because GetDefaultValue is protected internal.
But GetFallbackDefaultValue works for me, thank you!

@stakx
Copy link
Contributor Author

stakx commented Apr 5, 2018

I cannot use DefaultValueProvider.Empty because GetDefaultValue is protected internal.

(You cannot override, but you can use composition.)

GetFallbackDefaultValue works for me, thank you!

Glad to hear. You're welcome!

@Serg046
Copy link

Serg046 commented Apr 5, 2018

Do you mean DefaultValueProvider.Empty.GetDefaultValue(type, mock) call?

@stakx
Copy link
Contributor Author

stakx commented Apr 5, 2018

Sorry, you're right of course... I seem to have mis-remembered those methods' accessibility. Composition indeed doesn't seem possible in this case (which is probably a design flaw).

@krizex
Copy link

krizex commented May 4, 2023

Sorry, you're right of course... I seem to have mis-remembered those methods' accessibility. Composition indeed doesn't seem possible in this case (which is probably a design flaw).

If so, how can I make a customized value provider which falls back to the Mock object if I did not register the type?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants