在 AutoFixture 中注入接口和实现

Inject Both Interface and Implementation in 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 作为参数,它不起作用,因为冻结的 Class 会覆盖 ICustomization.Customize 方法中的注入值。

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

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

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

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

这告诉 AutoFixture 每次必须为它的 any 创建值时提供 same Class 实例实现的接口。

如果你绝对必须自己动手

如果仔细观察 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.

也被冻结

好的,这花了很长时间才弄明白,但这 question/scenario 最终是由于我的设计不佳以及对 AutoFixture 缺乏经验和学习。我尝试做的实际场景是在我的夹具中注册一个 IoC 容器,我希望 IServiceLocator 及其实现都在夹具中注册,以便它们可用于将值注入当前测试方法。

已通过添加 a Customization for relaying requests to a provided IServiceLocator (also mentioned ) 解决此问题。

此外,我确实制作了 an extension method that makes use of Dynamitey 来满足我的需求,但它已不再使用,我可能会在某个时候删除它。

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

最后,我要感谢@enrico-campidoglio 和@mark-seemann 的耐心,真的 informative/quality answers/assistance(也感谢没有人对这个问题投反对票)。我知道这里的门槛很高@Stack Overflow,在发布这个问题之前我可能应该多一点耐心。