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

Customize SubstituteFactory. #751

Closed
kekchpek opened this issue Oct 28, 2023 · 3 comments
Closed

Customize SubstituteFactory. #751

kekchpek opened this issue Oct 28, 2023 · 3 comments

Comments

@kekchpek
Copy link

I ran onto a problem.
In my program I work a lot with async code and I have my own types(including interfaces) to work with async/await keywords. I also use standard tasks library. So the interface of an object with async methods could look like this:

public interface IFooService {
    IPromise SomeAsyncMethod();
    IPromise SomeOtherAsyncMethod();
    Task SomeAsyncMethodWithTask();
}

NSubstitute mock every Task with a completed task by default. I'd like to make pretty same thing for my IPromise.
I surfed the internet and found couple of topics: this and other and one more, but didnt' found suitable solution there.

It will be cool if I could configure SubstituteFactory with some method like this:

SubstitutionContext.Current.SubstituteFactory.ConfigureDefaultFor<T>(Func<T> factoryMethod);

Or maybe this method should be a part of a context.

SubstitutionContext.Current.ConfigureDefaultFor<T>(Func<T> factoryMethod);

Or event provide an ability to rewrite SubstituteFactory with some proxy. It could solve other problems, when you want specific behaviour for mocks creation.

 public class SubstituteFactoryDecorator : ISubstituteFactory
{
    private readonly ISubstituteFactory _source;

    public SubstituteFactoryDecorator(ISubstituteFactory source)
    {
        _source = source;
    }

    public object Create(Type[] typesToProxy, object[] constructorArguments)
    {
        if (typesToProxy.Length == 1 && typesToProxy[0] == typeof(IPromise))
            return PromiseTool.GetSuccessful();
        return _source.Create(typesToProxy, constructorArguments);
    }

    public object CreatePartial(Type[] typesToProxy, object[] constructorArguments)
    {
        if (typesToProxy.Length == 1 && typesToProxy[0] == typeof(IPromise))
            return PromiseTool.GetSuccessful();
        return _source.CreatePartial(typesToProxy, constructorArguments);
    }
}
...

SubstitutionContext.Current.SubstituteFactory = new SubstituteFactoryDecorator(SubstitutionContext.Current.SubstituteFactory);

All of this are just my rough thoughts about the implementation. It could be completely different, but I hope my problem is clear. If NSubstitute already has solution for such kind of problems, please tell me.

@Ergamon
Copy link

Ergamon commented Oct 31, 2023

I am not aware of any inbuild NSubstitute feature for this.

Normally I use NSubstitute in conjunction with AutoFixture.

So basically instead of

var fooService = Substitute.For<IFooService>();

you have to write

var fooService = fixture.Create<IFooService>();

To make it work like you desire, just create your fixture e.g. like this:

var fixture = new Fixture();
    
var customization = new AutoNSubstituteCustomization
{
  ConfigureMembers = true,
  GenerateDelegates = true,
};
customization.Customize(fixture);

fixture.Inject<IPromise>(PromiseTool.GetSuccessful());

@dtchepak
Copy link
Member

Hi @kekchpek ,
There is one more thread that might be of use here: #705 (comment)
Could you check if that addresses this issue?

@kekchpek
Copy link
Author

kekchpek commented Nov 2, 2023

@Ergamon thank you for noticing such a good tool for testing as AutoFixture! It almost the thing I had been looking for. But I also need make substitutes for my generic promises, and I didn't find a way to do it with AutoFixture quickly.
@dtchepak thank you for your link! This is a suitable tool for me. Sad, that such powerful API of NSubstitute is not documented and it takes so much time to find it. I've made my own AutoValueProvider for promises. I post it here, in case it turns out to be useful for someone else.

...
// I run this one time before all tests.
        var customizedContainer = NSubstituteDefaultFactory.DefaultContainer.Customize();
        customizedContainer.Decorate<IAutoValueProvidersFactory>((factory, _) => new PromiseValueProviderFactory(factory));
        SubstitutionContext.Current = customizedContainer.Resolve<ISubstitutionContext>();
...

public class PromiseValueProviderFactory : IAutoValueProvidersFactory
{
    private readonly IAutoValueProvidersFactory _original;

    private class PromiseValueProvider : IAutoValueProvider
    {

        private readonly Dictionary<Type, object> _completedPromises = new();

        public bool CanProvideValueFor(Type type)
        {
            return type == typeof(IPromise) || type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IPromise<>);
        }

        public object GetValue(Type type)
        {
            if (type == typeof(IPromise))
                return PromiseTool.GetSuccessful();
            if (type.GetGenericTypeDefinition() == typeof(IPromise<>))
                return GetSuccessful(type.GenericTypeArguments.Single());
            throw new InvalidOperationException($"Can not create promise for type {type.Name}");
        }

        private object GetSuccessful(Type t)
        {
            if (_completedPromises.TryGetValue(t, out var p))
                return p;
            var promiseTool = typeof(PromiseTool);
            var getMethod = promiseTool.GetMethods(BindingFlags.Public | BindingFlags.Static)
                .Single(x => x.Name == nameof(PromiseTool.GetSuccessful) && x.IsGenericMethod);
            var completedPromise =
                Expression.Lambda(Expression.Call(
                    getMethod.MakeGenericMethod(t),
                    Expression.Default(t))).Compile().DynamicInvoke();
            _completedPromises.Add(t, completedPromise);
            return completedPromise;
        }
    }

    public PromiseValueProviderFactory(IAutoValueProvidersFactory original)
    {
        _original = original;
    }
    
    public IReadOnlyCollection<IAutoValueProvider> CreateProviders(ISubstituteFactory substituteFactory)
    {
        return new [] {new PromiseValueProvider()}.Concat(_original.CreateProviders(substituteFactory)).ToArray();
    }
}

@kekchpek kekchpek closed this as completed Nov 2, 2023
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

No branches or pull requests

3 participants