简体   繁体   中英

How to unit test decorator registrations using SimpleInjector 3+?

Follow up to an older question here. Say I have a registration like the following:

container.Register(typeof(IHandleCommand<>), _handlerAssemblies, Lifestyle.Transient);
container.RegisterDecorator(typeof(IHandleCommand<>),
  typeof(MetricsCommandHandlerWrapper<>), Lifestyle.Singleton);

Where the MetricsCommandHandlerWrapper is defined like so:

public class MetricsCommandHandlerWrapper<T> : IHandleCommand<T> where T: ICommand
{
    private readonly ICollectMetrics _metrics;
    private readonly Func<IHandleCommand<T>> _handlerFactory;

    public MetricsCommandHandlerWrapper(ICollectMetrics metrics,
      Func<IHandleCommand<T>> handlerFactory)
    {
      _metrics = metrics;
      _handlerFactory = handlerFactory;
    }

    public async Task HandleAsync(T command)
    {
        // code to record metrics around command handling which eventually invokes
        await _handlerFactory().HandleAsync(command).ConfigureAwait(false);
    }
}

How can I write a unit test that asserts the actual decoratee handlers are registered with Transient lifestyle?

I have tried composing the root and inspecting the registration for a closed IHandleCommand<FakeCommand> type, which reveals an ImplementationType of MetricsCommandHandlerWrapper<FakeCommand> as expected. Invoking GetRelationships() on that registration reveals its 2 dependencies, ICollectMetrics and the one I am interested in, the Func<IHandleCommand<FakeCommand>> factory delegate, which is registered as a Singleton . However invoking .Dependency.GetInstance() on that factory delegate throws an exception that the instance producer returned null.

Is there any way to assert that the inner decoratee is registered as Transient , and if so, how?

The use of the Func<T> delays the building of the object graph, and from perspective of the diagnostic system, the graphs stops at that point. So, it's not possible to do this analysis.

Instead of completely relying on Simple Injector's internals however, you can also choose to make some minor changing in your application to allow testing decorators.

What you can do is implement an IDecorator abstraction on your decorators:

public interface IDecorator {
    object Decoratee { get; }
}

Now each decorator can implement this interface. For the MetricsCommandHandlerWrapper<T> , it might look as follows:

public class MetricsCommandHandlerWrapper<T> : IHandleCommand<T>, IDecorator where T: ICommand
{
    private readonly ICollectMetrics _metrics;
    private readonly Func<IHandleCommand<T>> _handlerFactory;

    public MetricsCommandHandlerWrapper(ICollectMetrics metrics,
      Func<IHandleCommand<T>> handlerFactory) {
      _metrics = metrics;
      _handlerFactory = handlerFactory;
    }

    public object Decoratee { get { return _handlerFactory(); }

    public async Task HandleAsync(T command) { ... }
}

On top of the IDecorator interface, you can define a simple extension method:

public static IEnumerable<Type> GetDecoratorChain(this IDecorator decorator) {
    while (decorator != null) {
        yield return decorator.GetType();
        decorator = decorator.Decoratee as IDecorator;
    }
}

Inside your unit test you can now resolve a handler and ask for the list of applied decorators. Using this list you can verify whether decorators are applied in the correct order.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM