Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Inject Both Interface and Implementation in AutoFixture

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 );
}
like image 366
Mike-E Avatar asked Nov 13 '15 00:11

Mike-E


2 Answers

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.

like image 95
Enrico Campidoglio Avatar answered Nov 18 '22 10:11

Enrico Campidoglio


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.

like image 37
Mark Seemann Avatar answered Nov 18 '22 11:11

Mark Seemann