3

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+?

qujck
  • 14,388
  • 4
  • 45
  • 74
danludwig
  • 46,965
  • 25
  • 159
  • 237

2 Answers2

3

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());
}
qujck
  • 14,388
  • 4
  • 45
  • 74
  • 1
    Thanks for this. As for the capitve dependency, I don't think there is one. The singleton does not contain an instance of the transient decorator, but rather a `Func> handlerFactory` instance. The singleton implementation is set up to get a new transient instance of the decorator it wraps on each `Handle` method invocation. – danludwig Dec 27 '14 at 18:15
  • @danludwig fair enough! – qujck Dec 27 '14 at 18:38
  • I just tried this and the order of the relationships does not fit the order provided in the open generic decorator chain. The first two assertions is valid, but the `registration = registration.GetRelationships().Single().Dependency.Registration.ImplementationType;` returns `'System.Func[IHandleQuery[FakeQueryWithoutValidator]]` and not the expected `QueryNotNullDecorator`. Complete failure: http://pbrd.co/1rsLf9F – janhartmann Dec 28 '14 at 11:12
  • 1
    @meep my first thought is that if you have a `Func<>` delegate in the constructor then that is what you would expect to see as the dependency. The code above does not cater for delegate injection. Please feel free to post a new question on [so](http://stackoverflow.com/) of the [Simple Injector forum](https://simpleinjector.org/forum) and I will look into it for you. – qujck Dec 28 '14 at 13:12
  • Thanks for this, been away for 3 weeks because of vacation. I will try your suggesting out and provide feedback. – janhartmann Jan 21 '15 at 10:01
3

Qujck is right. We greatly improved the way registrations and KnownDependency graphs are built, especially to improve diagnostics and to make it easier for users to query the registrations. So this is not a bug; this is a breaking change. We however didn't expect anyone to be affected by this and that's why we made the change in a minor release. I'm sorry you had to stumble upon this change, but at least it's just test code that breaks.

In previous versions the graph of KnownDependency objects was flattened when decorators where added. This made querying and visualising the object graph hard. In v2.6, from perspective of the diagnostic API, it is as if a decorator is a 'real' registration. This means that the InstanceProducer and Registration objects now show the real type that is returned and its lifestyle.

Much clearer, but a breaking change in the diagnostic API.

qujck
  • 14,388
  • 4
  • 45
  • 74
Steven
  • 166,672
  • 24
  • 332
  • 435