在AutoFixture中注入接口和实现

时间:2015-11-13 00:18:00

标签: c# unit-testing autofixture

考虑以下课程:

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.
    }
}

我遇到的问题是IInterface是注入的,而Class则没有注入。有没有办法注入IInterfaceClass,以便为两者返回相同的实例?

请注意,我希望使用ICustomization(或ICustomization内)而不是测试方法的属性。我希望在这两个类上进行自定义注入。如果我使用[Frozen( Matching.ImplementedInterfaces)]Class item作为参数,则它不起作用,因为冻结的类会覆盖ICustomization.Customize方法中的注入值。

另请注意,这是示例代码,而不是我如何使用它。在xUnit测试方法中,我希望指定为参数的Class实例是上面冻结的IInstance

public void MyTest( IInterface @interface, Class implementation )
{
    Assert.Same( @interface, implementation );
}

3 个答案:

答案 0 :(得分:5)

如果你绝对必须自己做

如果你仔细观察Inject方法,你会注意到它实际上是一个泛型方法,但是当你使用它时就会推断出类型参数。如果你想要冻结两者,你可以 - 你只需要为每种类型调用Inject

这是一个略微修改过的Customization。为了防止可能无效的向下转发,我对其进行了更改,使其item字段(以及相应的item构造函数参数)的类型为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);
    }
}

请注意Customize两次注入相同的项目。在第一行中,泛型类型参数由编译器推断为Class,而在第二行中,类型参数IInterface被明确定义。

鉴于此实施,以下测试通过:

[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);
}

使用内置API

所有这一切,我认为简单地使用内置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);
}

TypeRelayIInterface映射到Class,这意味着IInterface的所有请求都会转发给Class的请求。冻结Class时,这意味着Class不仅被冻结,IInterface也是如此。

答案 1 :(得分:4)

当然,您可以在具体类参数上应用[Frozen]属性,并将ImplementedInterfaces指定为匹配条件:

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

告诉AutoFixture每次必须为其实现的接口的任何创建值时,提供相同的 Class实例。

答案 2 :(得分:0)

好的,这需要永远弄清楚,但这个问题/场景最终是由于我的设计不好以及缺乏经验和学习AutoFixture。我尝试做的实际情况是使用我的fixture注册一个IoC容器,我希望将IServiceLocator及其实现注册到Fixture,以便它们可用于将值注入当前的测试方法。

通过添加a Customization来将请求转发到提供的IServiceLocator(也提到here in this question),解决了这个问题。

此外,我确实使an extension method使用了我正在寻找的Dynamitey,但它已不再使用,我可能会在某些时候删除。

答案是:如果您出于某种原因想要这样做,则很可能doing it wrong 。我很想删除这个答案,但我会把它留在这里以防万一像我这样的新手遇到同样的问题并可能从中受益。

最后,我要感谢@ enrico-campidoglio和@ mark-seemann的耐心和真实的信息/质量答案/帮助(并且也没有人对此问题进行投票)。我知道这里的酒吧设置得很高@ Stack Overflow,在发布这个问题之前我可能应该有一点耐心。