Inject Both Interface and Implementation in AutoFixture
Asked Answered
S

3

6

Consider the following classes:

public interface IInterface {}
public class Class : IInterface {}

public class Customization : ICustomization
{
    readonly IInterface item;

    public Customization() : this( new Class() ) {}

    public Customization( IInterface item )
    {
        this.item = item;
    }

    public void Customize( IFixture fixture )
    {
        fixture.Inject( item );
        var created = fixture.Create<Class>(); // Would like this to resolve as item from previous line.
    }
}

The problem I am running into is that the IInterface is injected, whereas the Class is not. Is there a way to inject both IInterface and Class so that the same instance is returned for both?

Please note that I would like to do this using an ICustomization (or within an ICustomization) and not with the attributes on a test-method. I am looking to do customized inject on these two classes. If I use [Frozen( Matching.ImplementedInterfaces)]Class item as a parameter, it doesn't work, as the Class that is Frozen overwrites the injected value in the ICustomization.Customize method.

Please additionally note that this is sample code and not how I am using it. In the xUnit Test Method, I would like the Class instance that is specified as a parameter to be the frozen IInstance above:

public void MyTest( IInterface @interface, Class implementation )
{
    Assert.Same( @interface, implementation );
}
Stores answered 13/11, 2015 at 0:17 Comment(2)
That Freeze overload doesn't do what you think it does; see the documentation. See Enrico Campidoglio's answer for one fairly idiomatic way of achieving the desired result. Another option would be to use one of the AutoFixture auto-mocking container extensions, which basically have such features built-in.Potful
Apologies @MarkSeemann, I am fail. I did see the other discussion on Inject/Freeze and got confused. I did mean Inject and not Freeze, and have updated the question accordingly.Stores
S
0

OK this took forever to figure out, but this question/scenario is ultimately due to bad design on my part and inexperience to and learning AutoFixture. The actual scenario of what I was trying to do was register an IoC container with my fixture, and I wanted both the IServiceLocator and its implementation to be registered with the Fixture so that they are available for injecting values into the current test method.

This was resolved by adding a Customization for relaying requests to a provided IServiceLocator (also mentioned here in this question).

Additionally, I did make an extension method that makes use of Dynamitey that does what I was looking for, but it is no longer used and I will probably delete at some point.

So the answer is: if you are wanting to do this for some reason, you are more than likely doing it wrong. I am half-way tempted to delete this answer, but I will leave it here in case another newb like myself encounters the same problem and might benefit from this.

Finally, I would like to thank @enrico-campidoglio and @mark-seemann for their patience and really informative/quality answers/assistance (and also for no one down-voting this question). I know the bar is set high here @ Stack Overflow and I probably should have had a little more patience before posting this question.

Stores answered 23/11, 2015 at 9:24 Comment(0)
P
7

Sure, you can apply the [Frozen] attribute on the concrete class parameter and specify ImplementedInterfaces as matching criteria:

[Theory, AutoData]
public void Test(
    [Frozen(Matching.ImplementedInterfaces)]Class implementation,
    IInterface @interface)
{
    Assert.Same(implementation, @interface);
}

That tells AutoFixture to provide the same Class instance every time it has to create a value for any of its implemented interfaces.

Patchy answered 13/11, 2015 at 8:24 Comment(1)
Thank you for your answer, @enrico-campidoglio. I apologize for not being more clear, I am looking to Inject and not Freeze. Furthermore, I am looking to Inject within the ICustomization and not at the attribute-level. I have updated the question to be more concise/accurate. Please excuse my newbiness!Stores
P
5

If you absolutely must do it yourself

If you look closer at the Inject method, you'll notice that it's actually a generic method, but that the type argument is inferred when you use it like you use it. If you want to freeze both, you can - you'll just have to invoke Inject for each type.

Here's a slightly modified Customization. In order to prevent a possibly invalid downcast, I changed it so that its item field (and the corresponding item constructor argument) is of the type Class:

public class Customization : ICustomization
{
    readonly Class item;

    public Customization() : this(new Class()) { }

    public Customization(Class item)
    {
        this.item = item;
    }

    public void Customize(IFixture fixture)
    {
        fixture.Inject(item);
        fixture.Inject<IInterface>(item);
    }
}

Notice that Customize injects the same item twice. In the first line, the generic type argument is inferred to Class by the compiler, whereas in the second line, the type argument IInterface is explicitly defined.

Given this implementation, the following test passes:

[Fact]
public void UseCustomization()
{
    var fixture = new Fixture().Customize(new Customization());

    var c = fixture.Create<Class>();
    var i = fixture.Create<IInterface>();

    Assert.Equal(c, i);
}

Using the built-in API

All that said, I'd consider it easier to simply use the built-in API:

[Fact]
public void UseTypeRelay()
{
    var fixture = new Fixture();
    fixture.Customizations.Add(
        new TypeRelay(
            typeof(IInterface),
            typeof(Class)));
    fixture.Freeze<Class>();

    var c = fixture.Create<Class>();
    var i = fixture.Create<IInterface>();

    Assert.Equal(c, i);
}

TypeRelay maps IInterface to Class, which means that all requests for IInterface will be relayed to requests for Class. When Class is frozen, that means that not only is Class frozen, but so is IInterface.

Potful answered 13/11, 2015 at 19:29 Comment(0)
S
0

OK this took forever to figure out, but this question/scenario is ultimately due to bad design on my part and inexperience to and learning AutoFixture. The actual scenario of what I was trying to do was register an IoC container with my fixture, and I wanted both the IServiceLocator and its implementation to be registered with the Fixture so that they are available for injecting values into the current test method.

This was resolved by adding a Customization for relaying requests to a provided IServiceLocator (also mentioned here in this question).

Additionally, I did make an extension method that makes use of Dynamitey that does what I was looking for, but it is no longer used and I will probably delete at some point.

So the answer is: if you are wanting to do this for some reason, you are more than likely doing it wrong. I am half-way tempted to delete this answer, but I will leave it here in case another newb like myself encounters the same problem and might benefit from this.

Finally, I would like to thank @enrico-campidoglio and @mark-seemann for their patience and really informative/quality answers/assistance (and also for no one down-voting this question). I know the bar is set high here @ Stack Overflow and I probably should have had a little more patience before posting this question.

Stores answered 23/11, 2015 at 9:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.