NSubstitute
NSubstitute copied to clipboard
Decorate interface with proxing substitute
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
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);
In combination with a Scrutor this worked perfectly for me. I'll leave it here in the case is useful for someone else:
- 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;
}
-
Introduce the substitute extensions proposed above.
-
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;
});
@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() )
I assume your question has been answered, if not, please let us know!