3

Recently when trying out AutoFixture as an NSubstitute auto-mocking container I ran into what seems to be a surprising deficit in the implementation. Although substitutes to appear to be automatically generated for constructor/factory parameters that are specified as interfaced types, the generated substitutes/mocks do not appear to be automatically configured to return automatic values specified through the fixture as I would have expected.

To illustrate what I thought would work out of the box I've created a simple test below.

    [Test]
    public void MyClass_WhenAskedToDoSomething_ShouldReturnANumberFromSomeService()
    {
        // Arrange
        var fixture = new Fixture().Customize(new AutoNSubstituteCustomization());
        var expectedNumber = fixture.Freeze<int>();

        var sut = fixture.Create<MyClass>();

        // Act
        var number = sut.AskToDoSomething();

        // Assert
        Assert.IsTrue(number == expectedNumber);
    }

    public class MyClass
    {
        private readonly IMyInterface _myInterface;

        public MyClass(IMyInterface myInterface)
        {
            _myInterface = myInterface;
        }

        public int AskToDoSomething()
        {
            return _myInterface.GetService().GetNumber();
        }
    }

    public interface IMyInterface
    {
        ISomeService GetService();
    }

    public interface ISomeService
    {
        int GetNumber();   
    }

In the case that I'm expecting something that just isn't included in the AutoNSubstituteCustomization implementation, would something like this be difficult to pull off. Has anybody else treaded in this direction. Any pointers would help.

abatishchev
  • 98,240
  • 88
  • 296
  • 433
jpierson
  • 16,435
  • 14
  • 105
  • 149
  • 2
    This question is closely related to [this one](http://stackoverflow.com/q/18497489/126014), although that other question asks about Moq, and not NSubstitute. The answer may be near-identical, although I don't know NSubstitute well enough to say whether or not a sufficient extensibility point exists in NSubstitute. – Mark Seemann Jun 17 '14 at 20:30
  • So what I can gather is that the behavior that I'm seeing is expected and there may be a way to configure it to work as I originally anticipated? I'm looking over the implementation of AutoNSubstituteCustomization on GitHub right now. – jpierson Jun 17 '14 at 21:43
  • You don't need to look at AutoNSubstituteCustomization; it doesn't do what you want it to do. If you're really interested in this, you'll need to look NSubstitute over, to figure out whether it has an extensibility point, enabling other libraries to hook into its object creation routine. – Mark Seemann Jun 18 '14 at 05:29
  • 1
    It's not that we don't want AutoNSubstituteCustomization to do what you'd like it to do; it's that we (probably) *can't*. At least, I know it's not possible with Moq, and IIRC, Rhino Mocks, but I'm less sure that it's impossible with NSubstitute... However, my guess is that it is (impossible). With Foq, however, it *is* possible, but I can't remember if that feature ever made it into AutoFixture.AutoFoq... – Mark Seemann Jun 18 '14 at 05:33
  • It sounds like if there is not already some way to get it working in NSubstitute that there is an [active item](https://github.com/nsubstitute/NSubstitute/issues/150) on GitHub to introduce such capabilities. – jpierson Jun 18 '14 at 17:17
  • At this point I'm optimistic that this could be done with NSubstitute as is even if it does mean some level of unpleasant hacking to get it done. To me at this point seems to be the easy part. Understanding how to get the customization to do this dynamically for me is the part that I'm hung up on currently. – jpierson Jun 18 '14 at 17:20
  • 1
    Prob not much use to you, but AutoFoq and Foq 1.5+ can do this and it works v well [currently with some assembly required, in future potentially as default behavior](http://nikosbaxevanis.com/blog/2014/02/23/how-to-configure-autofoq-with-foqs-return-strategy/) – Ruben Bartelink Jun 20 '14 at 16:32

1 Answers1

0

Since I've taken a stab at this myself I figured I should post some of what I've come up with so far. Below are a set of types that allow applying a broad default value capability to individual NSubstitute substitute instances.

public interface IDefaultValueFactory
{
    T GetDefault<T>();
}

public static class NSubstituteDefaultValueConfigurator
{
    public static void Configure(Type substituteType, object substitute, IDefaultValueFactory valueFactory)
    {
        var type = typeof(NSubstituteDefaultValueConfigurator<>)
            .MakeGenericType(substituteType);

        var configurator = type
            .GetConstructor(new Type[] { typeof(IDefaultValueFactory) })
            .Invoke(new object[] { valueFactory });

        type.GetMethod("ConfigureDefaultReturnValuesForAllMethods")
            .Invoke(configurator, new object[] { substitute });
    }
}


public class NSubstituteDefaultValueConfigurator<T>
{
    private readonly IDefaultValueFactory _valueFactory;

    public NSubstituteDefaultValueConfigurator(IDefaultValueFactory valueFactory)
    {
        _valueFactory = valueFactory;
    }

    private object GetDeafultValue<TResult>()
    {
        return _valueFactory.GetDefault<TResult>();
    }


    public void ConfigureDefaultReturnValuesForAllMethods(T substitute)
    {
        var interfaces = substitute
            .GetType()
            .GetInterfaces()
            // HACK: Specifically exclude supporting interfaces from NSubstitute
            .Where(i =>
                i != typeof(Castle.DynamicProxy.IProxyTargetAccessor) &&
                i != typeof(ICallRouter) /*&&
                i != typeof(ISerializable)*/)
            .ToArray();

        var methods = interfaces
            .SelectMany(i => i.GetMethods())
            .Where(m => m.ReturnType != typeof(void))

            // BUG: skipping over chained interfaces in NSubstitute seems
            // to cause an issue with embedded returns. Using them however
            // causes the mock at the end or along a chained call not to be
            // configured for default values.
            .Where(m => !m.ReturnType.IsInterface);

        foreach (var method in methods)
        {
            var typedConfigureMethod = this
                .GetType()
                .GetMethod("ConfigureDefaultReturnValuesForMethod", BindingFlags.NonPublic | BindingFlags.Static)
                .MakeGenericMethod(method.ReturnType);

            var defaultValueFactory = new Func<CallInfo, object>(
                callInfo => this
                    .GetType()
                    .GetMethod("GetDeafultValue", BindingFlags.NonPublic | BindingFlags.Instance)
                    .MakeGenericMethod(method.ReturnType)
                    .Invoke(this, null));

            typedConfigureMethod.Invoke(
                this,
                new object[]
                    {
                        substitute, 
                        defaultValueFactory,
                        method
                    });
        }

        //var properties = interfaces.SelectMany(i => i.GetProperties());
        var properties = substitute
            .GetType().GetProperties();

        foreach (var property in properties)
        {
            var typedConfigureMethod = this
                .GetType()
                .GetMethod("ConfigureDefaultReturnValuesForProperty", BindingFlags.NonPublic | BindingFlags.Static)
                .MakeGenericMethod(property.PropertyType);

            var defaultValueFactory = new Func<CallInfo, object>(
                callInfo => this
                    .GetType()
                    .GetMethod("GetDeafultValue", BindingFlags.NonPublic | BindingFlags.Instance)
                    .MakeGenericMethod(property.PropertyType)
                    .Invoke(this, null));

            typedConfigureMethod.Invoke(
                this,
                new object[]
                    {
                        substitute, 
                        defaultValueFactory,
                        property
                    });
        }
    }

    private static void ConfigureDefaultReturnValuesForMethod<TResult>(
        T substitute,
        Func<CallInfo, object> defaultValueFactory,
        MethodInfo method)
    {
        var args = method
            .GetParameters()
            .Select(p => GetTypedAnyArg(p.ParameterType))
            .ToArray();

        // Call the method on the mock
        var substituteResult = method.Invoke(substitute, args);

        var returnsMethod = typeof(SubstituteExtensions)
            .GetMethods(BindingFlags.Static | BindingFlags.Public)
            .First(m => m.GetParameters().Count() == 2)
            .MakeGenericMethod(method.ReturnType);

        var typedDefaultValueFactory = new Func<CallInfo, TResult>(callInfo => (TResult)defaultValueFactory(callInfo));

        returnsMethod.Invoke(null, new[] { substituteResult, typedDefaultValueFactory });
    }

    private static void ConfigureDefaultReturnValuesForProperty<TResult>(
        T substitute,
        Func<CallInfo, object> defaultValueFactory,
        PropertyInfo property)
    {
        // Call the property getter on the mock
        var substituteResult = property.GetGetMethod().Invoke(substitute, null);

        var returnsMethod = typeof(SubstituteExtensions)
            .GetMethods(BindingFlags.Static | BindingFlags.Public)
            .First(m => m.GetParameters().Count() == 2)
            .MakeGenericMethod(property.PropertyType);

        var typedDefaultValueFactory = new Func<CallInfo, TResult>(callInfo => (TResult)defaultValueFactory(callInfo));

        returnsMethod.Invoke(null, new[] { substituteResult, typedDefaultValueFactory });
    }

    private static object GetTypedAnyArg(Type argType)
    {
        return GetStaticGenericMethod(typeof(Arg), "Any", argType);
    }

    private static MethodInfo GetStaticGenericMethod(
        Type classType,
        string methodName,
        params Type[] typeParameters)
    {
        var method = classType
            .GetMethod(methodName, BindingFlags.Static | BindingFlags.Public)
            .MakeGenericMethod(typeParameters);

        return method;
    }
}

Since the Configure method is required to be called for each individual substitute instance there either needs to be be some intrusive modification of the supporting classes in the AutoFixture AutoNSubstitute supporting classes or a replacement implementation of AutoNSubstitute needs to be provided. In my tinkering directly within the AutoNSubstitute source code I modified the NSubstituteBuilder class as follows to adapt it to have configurable default/auto value capability.

    public object Create(object request, ISpecimenContext context)
    {
        if (!SubstitutionSpecification.IsSatisfiedBy(request))
            return new NoSpecimen(request);

        var substitute = Builder.Create(request, context);
        if (substitute == null)
            return new NoSpecimen(request);

        NSubstituteDefaultValueConfigurator.Configure(
            substitute.GetType(), 
            substitute,
            new AutoFixtureDefaultValueFactory(context));

        return substitute;
    }

    private class AutoFixtureDefaultValueFactory : IDefaultValueFactory
    {
        private readonly ISpecimenContext _context;

        public AutoFixtureDefaultValueFactory(ISpecimenContext context)
        {
            _context = context;
        }

        public T GetDefault<T>()
        {
            return _context.Create<T>();
        }
    }

Unfortunately there is either a bug in my implementation that handles reflection calls to property getters on substitutes or NSubstitute has a difference of handling properties than methods but I've run into a bit of a roadblock either way. The remaining issue is that with chained interfaces (interfaces that return other interfaces from their members) that a CouldNotSetReturnException is thrown when a concrete class that should be resolved through AutoFixture is encountered on a leaf property call. This only appears to happen for properties and not methods though which is both interesting and unfortunate. Given what appears to be a limitation in NSubsitute Returns method design and also a limitation in the general API for configuring default values more broadly.

So at this point it appears that the answer is no, out of the box the AutoNSubstitute customization for AutoFixture does not support the capability of returning the same automatic values returned by the fixture through the members of the returned substitutes. On the other hand, it appears that the maintainer of AutoFixture is willing to accept and perhaps support a reasonable implementation of this feature and I've been able to show that I can achieve at least a partially working implementation using the available facilities of NSubstitute without modification.

As a side note, a pattern that seems to be obvious to me is that mocking libraries that use static factories to create mocks and that do not have any type of instance based context naturally lack the ability to configure behavior of generated mocks per test. I thought about this limitation early on when first adopting mocks within unit tests and this is the first time it appears to be causing a problem for me.

Community
  • 1
  • 1
jpierson
  • 16,435
  • 14
  • 105
  • 149