如何使用 AutoFixture 注册装饰器?

How do I register decorators with AutoFixture?

Decorator pattern demonstrates how we can extend the behaviour of a component without modifying the underlying implementation. But this means I have two components that implement the same interface. Is there a way to Register these in AutoFixture 这样我仍然可以多态地引用我的组件作为它的接口?

一个代码示例帮助我理解了我的意思。假设我想用 LoggingComponent

装饰 IComponent
interface IComponent
{
    void DoStuff();
}

class Component : IComponent
{
    public void DoStuff()
    {
        // Do something amazing!
    }
}

class LoggingComponent : IComponent
{
    private readonly IComponent _Component;

    public LoggingComponent(IComponent Component)
    {
        _Component = Component;
    }

    public void DoStuff()
    {
        Console.WriteLine("Calling DoStuff()");
        _Component.DoStuff();
    }
}

我如何将 LoggingComponent 注册为 IComponent 并创建没有循环依赖的 LoggingComponent

用这种方式注册 LoggingComponent 还有什么选择:

_fixture.Register<IComponent>(_fixture.Create<LoggingComponent>);

然后尝试使用以下代码创建 IComponent 的实例:

var Component = _fixture.Create<IComponent>();

这显然会导致抛出此 ObjectCreationException 异常:

AutoFixture was unable to create an instance of type Ploeh.AutoFixture.Kernel.SeededRequest because the traversed object graph contains a circular reference. Information about the circular path follows below. This is the correct behavior when a Fixture is equipped with a ThrowingRecursionBehavior, which is the default. This ensures that you are being made aware of circular references in your code. Your first reaction should be to redesign your API in order to get rid of all circular references. However, if this is not possible (most likely because parts or all of the API is delivered by a third party), you can replace this default behavior with a different behavior: on the Fixture instance, remove the ThrowingRecursionBehavior from Fixture.Behaviors, and instead add an instance of OmitOnRecursionBehavior.

您需要先冻结 IComponent

然后,AutoFixture 将在创建 LoggingDecorator class.

时重新使用相同的 frozen IComponent 实例

这里有两种方法:

通过自动模拟使用 AutoFixture:

PM> Install-Package AutoFixture.AutoMoq

[Fact]
public void UsingAutoFixtureAutoMoq()
{
    var fixture = new Fixture()
        .Customize(new AutoMoqCustomization());
    var expected = fixture.Freeze<Mock<IComponent>>().Object;
    var decorator = fixture.Create<LoggingComponent>(); 

    var actual = decorator.Component;

    Assert.Equal(expected, actual);
}

将 AutoFixture 与 Auto Mocking 和 xUnit.net 数据理论结合使用:

PM> Install-Package AutoFixture.Xunit
PM> Install-Package AutoFixture.AutoMoq

[Theory, TestConventions]
public void UsingAutoFixtureAutoMoqWithXunitTheories(
    [Frozen]Mock<IComponent> inner,
    LoggingComponent decorator)
{
    var expected = inner.Object;
    var actual = decorator.Component;
    Assert.Equal(expected, actual);
}

TestConventions 属性 class 定义为:

internal class TestConventionsAttribute : AutoDataAttribute
{
    internal TestConventionsAttribute()
        : base(
            new Fixture().Customize(
                new AutoMoqCustomization()))
    {
    }
}

注意:要编译和运行上述测试,为IComponent添加一个Inspection PropertyLoggingDecorator.