NSubstitute icon indicating copy to clipboard operation
NSubstitute copied to clipboard

Decorate interface with proxing substitute

Open ealeykin opened this issue 3 years ago • 3 comments

Hi,

The question comes from asp net core integration test, lets say I've got a IDistributedCache with an in-memory implementation registered for unit tests purpose. I would like to have a possibility to check method invocation on IDistributedCache, but since it's backed with real in-memory implementation this is not possible with normal NSubstitue approach.

Is it possible to create a substitute over a registered interface (abstraction over a real class impl), so that I can use cache.Received(x).SetStringAsync(...) but having the actual implementation being invoked - basically a proxy decorator with all NSubstitute features supported?

Thanks Eugene

ealeykin avatar Jan 21 '22 21:01 ealeykin

Not really heavily tested, but you can probably try your luck with an extension like this:

  public static class SubstituteExtensions
  {
    public static void Decorate<T>(this T substitute, T decoratee)
      where T : class
    {
      ICallHandler factory(ISubstituteState state) => new DecorationHandler(decoratee);

      var router = SubstitutionContext.Current.GetCallRouterFor(substitute);
      router.RegisterCustomCallHandlerFactory(factory);
    }

    private class DecorationHandler : ICallHandler
    {
      public DecorationHandler(object decoratee)
      {
        Decoratee = decoratee;
      }

      public object Decoratee { get; }

      RouteAction ICallHandler.Handle(ICall call)
      {
        var methodInfo = call.GetMethodInfo();
        var arguments = call.GetArguments();
        var result = methodInfo.Invoke(Decoratee, arguments);

        return RouteAction.Return(result);
      }
    }
  }

So you can use it like this (assuming you have a class Foo implementing IFoo): var foo = new Foo(); var substitute = Substitute.For<IFoo>(); substitute.Decorate(foo);

Ergamon avatar Jan 21 '22 23:01 Ergamon

In combination with a Scrutor this worked perfectly for me. I'll leave it here in the case is useful for someone else:

  1. Added a generic method to decorate a given already registered type, is likely any tool that supports decorating would allow this, but this one uses the Scrutor decoratorFactory overload.
// Somewhere within my application assembly

public static IServiceCollection DecorateRegisteredType<T>(this IServiceCollection services, Func<T, T> decoratorFactory)
{
    services.Decorate(typeof(T), o => decoratorFactory((T)o));
    return services;
}
  1. Introduce the substitute extensions proposed above.

  2. In my tests replace a previously registered type with an NSubstitue decorated one:

services.DecorateRegisteredType<IDomainEventPublisher>(o =>
{
    var substitute = Substitute.For<IDomainEventPublisher>();
     substitute.Decorate(o);
     return substitute;
});

jhenriquez avatar Apr 20 '23 04:04 jhenriquez

@Ergamon would love to see something like this - makes testing much easier, cleaner and simplify the maintance of test code by a lot. you can move all 'Returns' setup code in the proxy class constructor.

Decorate sounds like a great addition to the Substitute methods ( For, ForPartsOf ) Substitute.Decorate<IInterface>( new FakeImplementation() )

puschie286 avatar Sep 19 '23 10:09 puschie286

I assume your question has been answered, if not, please let us know!

304NotModified avatar Apr 29 '24 12:04 304NotModified