diff --git a/test/Scrutor.Tests/DecorationTests.cs b/test/Scrutor.Tests/DecorationTests.cs index 087bc5ec..ed281ded 100644 --- a/test/Scrutor.Tests/DecorationTests.cs +++ b/test/Scrutor.Tests/DecorationTests.cs @@ -158,6 +158,59 @@ public void DisposableServicesAreDisposed() Assert.True(decorator.Inner.WasDisposed); } + [Fact] + public void ServicesWithSameServiceTypeAreOnlyDecoratedOnce() + { + // See issue: https://github.com/khellang/Scrutor/issues/125 + + bool IsHandlerButNotDecorator(Type type) + { + var isHandlerDecorator = false; + + var isHandler = type.GetInterfaces().Any(i => + i.IsGenericType && + i.GetGenericTypeDefinition() == typeof(IEventHandler<>) + ); + + if (isHandler) + { + isHandlerDecorator = type.GetInterfaces().Any(i => i == typeof(IHandlerDecorator)); + } + + return isHandler && !isHandlerDecorator; + } + + var provider = ConfigureProvider(services => + { + // This should end up with 3 registrations of type IEventHandler. + services.Scan(s => + s.FromAssemblyOf() + .AddClasses(c => c.Where(IsHandlerButNotDecorator)) + .AsImplementedInterfaces() + .WithTransientLifetime()); + + // This should not decorate each registration 3 times. + services.Decorate(typeof(IEventHandler<>), typeof(MyEventHandlerDecorator<>)); + }); + + var instances = provider.GetRequiredService>>().ToList(); + + Assert.Equal(3, instances.Count); + + Assert.All(instances, instance => + { + var decorator = Assert.IsType>(instance); + + // The inner handler should not be a decorator. + Assert.IsNotType>(decorator.Handler); + + // The return call count should only be 1, we've only called Handle on one decorator. + // If there were nested decorators, this would return a higher call count as it + // would increment at each level. + Assert.Equal(1, decorator.Handle(new MyEvent())); + }); + } + [Fact] public void DecoratingNonRegisteredServiceThrows() { @@ -227,5 +280,66 @@ public void Dispose() WasDisposed = true; } } + + public interface IEvent + { + } + + public interface IEventHandler where TEvent : class, IEvent + { + int Handle(TEvent @event); + } + + public interface IHandlerDecorator + { + } + + public sealed class MyEvent : IEvent + {} + + internal sealed class MyEvent1Handler : IEventHandler + { + private int _callCount; + + public int Handle(MyEvent @event) + { + return _callCount++; + } + } + + internal sealed class MyEvent2Handler : IEventHandler + { + private int _callCount; + + public int Handle(MyEvent @event) + { + return _callCount++; + } + } + + internal sealed class MyEvent3Handler : IEventHandler + { + private int _callCount; + + public int Handle(MyEvent @event) + { + return _callCount++; + } + } + + internal sealed class MyEventHandlerDecorator : IEventHandler, IHandlerDecorator where TEvent: class, IEvent + { + public readonly IEventHandler Handler; + + public MyEventHandlerDecorator(IEventHandler handler) + { + Handler = handler; + } + + public int Handle(TEvent @event) + { + return Handler.Handle(@event) + 1; + } + } } }