在AutoFixture中注入接口和实现

Mik*_*EEE 5 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.
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

public void MyTest( IInterface @interface, Class implementation )
{
    Assert.Same( @interface, implementation );
}
Run Code Online (Sandbox Code Playgroud)

Enr*_*lio 7

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

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

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


Mar*_*ann 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,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);
}
Run Code Online (Sandbox Code Playgroud)

使用内置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);
}
Run Code Online (Sandbox Code Playgroud)

TypeRelay映射IInterfaceClass,这意味着所有请求IInterface都将被转发给请求Class.当Class冻结时,这意味着不仅Class冻结,而且冻结IInterface.


Mik*_*EEE 0

好吧,这花了很长时间才弄清楚,但这个问题/场景最终是由于我的设计不佳以及对 AutoFixture 缺乏经验和学习造成的。我试图做的实际场景是向我的固定装置注册一个 IoC 容器,并且我希望 IServiceLocator 及其实现都向固定装置注册,以便它们可用于将值注入到当前的测试方法中。

通过添加用于将请求中继到提供的 IServiceLocator 的自定义(在本问题中也提到过)解决了这个问题。

此外,我确实制作了一个使用Dynamitey的扩展方法来完成我正在寻找的功能,但它不再使用,我可能会在某个时候删除。

所以答案是:如果你出于某种原因想要这样做,那么你很可能做错了。我很想删除这个答案,但我会将其留在这里,以防像我这样的另一个新手遇到同样的问题并可能从中受益。

最后,我要感谢@enrico-campidoglio 和@mark-seemann 的耐心和真正信息丰富/高质量的答案/帮助(也感谢没有人对这个问题投反对票)。我知道这里的标准很高@Stack Overflow,在发布这个问题之前我可能应该多一点耐心。