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

Improve ForPartsOf to use interface and implementation class instead of class only #625

Open
wsaeed opened this issue Jun 23, 2020 · 6 comments

Comments

@wsaeed
Copy link

wsaeed commented Jun 23, 2020

Currently ForPartsOf accepts a single template parameter (a class), which requires the mocked method to be virtual. Making methods of a class virtual allows derived classes to override which may change the behavior of the class.

TImplementation Substitute.For<TImplementation>(...)

https://nsubstitute.github.io/help/partial-subs/

Suggestion is to to have an overload similar to

TInterface Substitute.For<TInterface, TImplementation>(...)

Any methods which are not mocked forwarded to the implementation class.

@dtchepak
Copy link
Member

Hi @wsaeed ,
Do you have an example showing what you'd like to achieve with this?
We have some more options for to call base methods from a normal sub (created with Substitute.For), so there may be easier ways of doing what you're after.

@wsaeed
Copy link
Author

wsaeed commented Jun 30, 2020

Hi @dtchepak

public interface ITarget
{
      public string GetTargetPath(string abc);
      public bool SaveTarget(string abc);
}

public class TargetImpl : ITarget
{
      public string GetTargetPath(string abc)
      {
      }
      public virtual bool SaveTarget(string abc) // Note: SaveTarget is virtual
      {
      }
}
class Test
{
    public void Test1()
    {
            TargetImpl log = Substitute.ForPartsOf<TargetImpl>();
            log.Configure().SaveTarget(null).Return(true); // Mocked call

            void targetPath = log.GetTargetPath("abc"); // routed to actual implementation
            ...
     }
}

In the above example I have to declare TargetImpl.SaveTarget as virtual for mocking.

It will be really nice if I can do something similar to

public class TargetImpl : ITarget
{
      public string GetTargetPath(string abc)
      {
      }
      public bool SaveTarget(string abc) // Note: SaveTarget isn't virtual
      {
      }
}


class Test
{
    public void Test1()
    {
            ITarget log = Substitute.ForPartsOf<ITarget, TargetImpl>(); // Note: returning ITarget
            log.SaveTarget(null).Returns(true); // mocked call OR
            // log.Configure().SaveTarget(null).Return(true)
 
            void targetPath = log.GetTargetPath("abc"); // routed to actual implementation in TargetImpl
            ...
     }
}  

Let me know if you need more clarification

@oldmine
Copy link

oldmine commented Jul 2, 2020

For example, calling wrapped methods in FakeItEasy (https://fakeiteasy.readthedocs.io/en/stable/calling-wrapped-methods/).
It should forward not configured calls to wrapped object. And it's very useful when i want to change behavior a part of object that implement interface.

@dtchepak
Copy link
Member

dtchepak commented Jul 6, 2020

Thanks for the example @wsaeed. Is it sufficient to configure this forwarding using Returns?

    var mock = Substitute.For<ITarget>();
    var impl = new TargetImpl();
    mock.SaveTarget(null).ReturnsForAnyArgs(x => impl.SaveTarget(x.Arg<string>()));

@oldmine Thank you for the FIE example. This is a little trickier in NSubstitute due to the syntax. We would need to use Configure() to avoid calling the wrapped object while setting up calls.

@rbeurskens
Copy link

Not sure if this what you are looking for, but this may be a workaround if you want to be able to get a partial for a class that implements an interface, where interface members will be routed to the class implementations (which do not have to be virtual - just not explicit implementations, as these will be hidden by the mock)

If you look at the method PartialFor<>, it calls the SubstitutionContext.Current.SubstituteFactory.CreatePartial method with one type. If you call it with more types (one class and one or more interfaces) it will create the partial, but does not forward calls made through an interface reference to the implementation class

    public static T ForPartsOf<T>(params object[] constructorArguments) where T : class => (T) SubstitutionContext.Current.SubstituteFactory.CreatePartial(new Type[1]
    {
      typeof (T)
    }, constructorArguments);

With some research, I found something like this to forward all calls from a substitute to another object:

   public static class SubstituteExtensions
    {
        public static T ForwardCallsTo<T>(this T subst, object instance)
        {
            SubstitutionContext.Current.GetCallRouterFor(subst)
                .RegisterCustomCallHandlerFactory(state => new RedirectToInstanceHandler(instance));
            return subst;
        }


        private class RedirectToInstanceHandler : ICallHandler
        {
            private readonly object _instance;
            public RedirectToInstanceHandler(object instance)
            {
                _instance = instance;
            }
            public RouteAction Handle(ICall call)
            {
                // Look if the called method is implemented on the instance.
                var methodInfo = call.GetMethodInfo();
                MethodInfo methodOnInstance = null;
                
                if (methodInfo.ReflectedType.IsInterface)
                {
                    if (call.Target() != _instance // prevent stackoverflow caused by forwarding to itself
                        && methodInfo.ReflectedType.IsInstanceOfType(_instance))
                        methodOnInstance = methodInfo; // otherwise, forward to interface implementation on other object
                } else // find implementing method called from the class
                    methodOnInstance = GetInterfaceDeclarationsForMethod(methodInfo).FirstOrDefault(i => i.DeclaringType.IsInstanceOfType(_instance));

                if (methodOnInstance != null) // If so, forward the call to the corresponding method on the instance.
                    return RouteAction.Return(methodOnInstance.Invoke(_instance, call.GetArguments()));
                return RouteAction.Continue(); // If not, do nothing. (fallback to default)
            }
        }

        private static IEnumerable<InterfaceMapping> GetAllInterfaceMaps(Type aType) =>
            aType.GetTypeInfo()
                .ImplementedInterfaces
                .Select(aType.GetInterfaceMap);
        
        private static IEnumerable<MethodInfo> GetInterfaceDeclarationsForMethod(MethodInfo mi) =>
            GetAllInterfaceMaps(mi.ReflectedType)
                .SelectMany(map => Enumerable.Range(0, map.TargetMethods.Length)
                    .Where(n => map.TargetMethods[n] == mi)
                    .Select(n => map.InterfaceMethods[n]));
    }

Note it will not forward calls that are implemented as explicit if you let the substitute interface forward to itself:

var subst = (IList<int>)SubstitutionContext.Current.SubstituteFactory.CreatePartial(new[] { typeof(IList<int>), typeof(List<int>) }); // now we have a substitute inherited from List<int> and able to intercept IList<int>, though IList<> calls won't be forwarded to List<int> implementation (yet).
subst.ForwardCallsTo(subst); // here we make sure it does, using the custom helper, and we can still inspect received calls etc. as long as calls are made through the interface and not the class. (as most of the class members are not virtual)
 // some we still need to add by hand if we need them as they are implemented explicitly by List<int>
((IEnumerable)subst).GetEnumerator().Returns(_ => ((List<int>)subst).GetEnumerator());
subst.GetEnumerator().Returns(_ => ((List<int>)subst).GetEnumerator());

It can also be used like this if your substitute does not have to be a subclass of a certain class, but just forwarding calls is enough (for example when testing code that does not rely on the object being of type List, but IList instead) - Explicit implemented members will just get forwarded here, as the target object is not the same object as the mock. This way can even have one substitute forward members from multiple interfaces each to different objects that implement them.

var wrapped = new List<int> {1,2,3};
var subst = Subsitute.For<IList<int>>();
subst.ForwardCallsTo(wrapped);

@marcoregueira
Copy link

marcoregueira commented Jul 21, 2022

Hi!

I've sent you a pull request implementing this feature (#700).

I hope you find it useful.

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

5 participants