【问题标题】:Inject Both Interface and Implementation in AutoFixture在 AutoFixture 中注入接口和实现
【发布时间】:2015-11-13 00:17:59
【问题描述】:

考虑以下类:

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

【问题讨论】:

  • Freeze 重载并没有像你想象的那样做;见the documentation。有关实现所需结果的一种相当惯用的方法,请参见 Enrico Campidoglio 的答案。另一种选择是使用 AutoFixture 自动模拟容器扩展之一,它基本上内置了这些功能。
  • 道歉@MarkSeemann,我失败了。我确实看到了关于 Inject/Freeze 的其他讨论并且感到困惑。我的意思是注入而不是冻结,并相应地更新了问题。

标签: c# unit-testing autofixture


【解决方案1】:

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

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

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

【讨论】:

  • 感谢您的回答,@enrico-campidoglio。我很抱歉没有更清楚,我正在寻找注入而不是冻结。此外,我希望在 ICustomization 中注入,而不是在属性级别。我已将问题更新为更简洁/准确。请原谅我的新手!
【解决方案2】:

如果你绝对必须自己做

如果您仔细观察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 也被冻结。

【讨论】:

    【解决方案3】:

    好的,这需要很长时间才能弄清楚,但是这个问题/场景最终是由于我的糟糕设计以及对 AutoFixture 的缺乏经验和学习。我尝试做的实际场景是向我的夹具注册一个 IoC 容器,我希望 IServiceLocator 及其实现都向夹具注册,以便它们可用于将值注入当前测试方法。

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

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

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

    最后,我要感谢 @enrico-campidoglio 和 @mark-seemann 的耐心和非常有用/高质量的答案/帮助(也感谢没有人反对这个问题)。我知道@Stack Overflow 这里的门槛很高,在发布这个问题之前我可能应该多一点耐心。

    【讨论】:

      猜你喜欢
      • 2019-04-09
      • 2015-10-18
      • 2020-10-09
      • 1970-01-01
      • 2016-07-25
      • 1970-01-01
      • 2020-04-09
      • 1970-01-01
      • 2020-05-22
      相关资源
      最近更新 更多