Search code examples
c#unit-testingdecoratorsimple-injectoropen-generics

How to unit test open generic decorator chains in SimpleInjector 2.6.1+


Given the following open generic deocrator chain using SimpleInjector:

container.RegisterManyForOpenGeneric(typeof(IHandleQuery<,>), assemblies);
container.RegisterDecorator(
    typeof(IHandleQuery<,>),
    typeof(ValidateQueryDecorator<,>)
);
container.RegisterSingleDecorator(
    typeof(IHandleQuery<,>),
    typeof(QueryLifetimeScopeDecorator<,>)
);
container.RegisterSingleDecorator(
    typeof(IHandleQuery<,>),
    typeof(QueryNotNullDecorator<,>)
);

With SimpleInjector 2.4.0, I was able to unit test this to assert the decoration chain using the following code:

[Fact]
public void RegistersIHandleQuery_UsingOpenGenerics_WithDecorationChain()
{
    var instance = Container
        .GetInstance<IHandleQuery<FakeQueryWithoutValidator, string>>();
    InstanceProducer registration = Container.GetRegistration(
        typeof(IHandleQuery<FakeQueryWithoutValidator, string>));

    instance.ShouldNotBeNull();
    registration.Registration.ImplementationType
        .ShouldEqual(typeof(HandleFakeQueryWithoutValidator));
    registration.Registration.Lifestyle.ShouldEqual(Lifestyle.Transient);
    var decoratorChain = registration.GetRelationships()
        .Select(x => new
        {
            x.ImplementationType,
            x.Lifestyle,
        })
        .Reverse().Distinct().ToArray();
    decoratorChain.Length.ShouldEqual(3);
    decoratorChain[0].ImplementationType.ShouldEqual(
        typeof(QueryNotNullDecorator<FakeQueryWithoutValidator, string>));
    decoratorChain[0].Lifestyle.ShouldEqual(Lifestyle.Singleton);
    decoratorChain[1].ImplementationType.ShouldEqual(
        typeof(QueryLifetimeScopeDecorator<FakeQueryWithoutValidator, string>));
    decoratorChain[1].Lifestyle.ShouldEqual(Lifestyle.Singleton);
    decoratorChain[2].ImplementationType.ShouldEqual(
        typeof(ValidateQueryDecorator<FakeQueryWithoutValidator, string>));
    decoratorChain[2].Lifestyle.ShouldEqual(Lifestyle.Transient);
}

After updating to SimpleInjector 2.6.1, this unit test fails. It seems that InstanceProducer.Registration.ImplementationType now returns the first decoration handler rather than the decorated handler (meaning, it returns typeof(QueryNotNullDecorator<HandleFakeQueryWithoutValidator,string>) instead of typeof(HandleFakeQueryWithoutValidator).

Also, InstanceProducer.GetRelationships() no longer returns all of the decorators in the chain. it also only returns the first decorator.

Is this a bug and, if not, how can we unit test open generic decorator chains using SimpleInjector 2.6.1+?


Solution

  • The detail available for the dependency graph has been greatly improved in 2.6. You can achieve the same thing with this code:

    [Fact]
    public void RegistersIHandleQuery_UsingOpenGenerics_WithDecorationChain()
    {
        var container = this.ContainerFactory();
    
        var instance = container
            .GetInstance<IHandleQuery<FakeQueryWithoutValidator, string>>();
    
        var registration = (
            from currentRegistration in container.GetCurrentRegistrations()
            where currentRegistration.ServiceType ==
                typeof(IHandleQuery<FakeQueryWithoutValidator, string>)
            select currentRegistration.Registration)
            .Single();
        Assert.Equal(
            typeof(QueryNotNullDecorator<FakeQueryWithoutValidator, string>), 
            registration.ImplementationType);
        Assert.Equal(Lifestyle.Singleton, registration.Lifestyle);
    
        registration = registration.GetRelationships().Single().Dependency.Registration;
        Assert.Equal(
            typeof(QueryLifetimeScopeDecorator<FakeQueryWithoutValidator, string>), 
            registration.ImplementationType);
        Assert.Equal(Lifestyle.Singleton, registration.Lifestyle);
    
        registration = registration.GetRelationships().Single().Dependency.Registration;
        Assert.Equal(
            typeof(ValidateQueryDecorator<FakeQueryWithoutValidator, string>), 
            registration.ImplementationType);
        Assert.Equal(Lifestyle.Transient, registration.Lifestyle);
    }
    

    You can find more information here

    Please note: I think you have a captive dependency - you have a transient handler inside of a singleton decorator ...

    [Fact]
    public void Container_Always_ContainsNoDiagnosticWarnings()
    {
        var container = this.ContainerFactory();
    
        container.Verify();
    
        var results = Analyzer.Analyze(container);
    
        Assert.False(results.Any());
    }