关于c#:如何在AutoFixture中注册装饰器?

How do I register decorators with AutoFixture?

Decorator模式演示了如何在不修改基础实现的情况下扩展组件的行为。但这意味着我有两个实现相同接口的组件。有没有一种方法可以在AutoFixture中对这些元素进行Register,这样我仍然可以将我的组件多态地称为其接口?

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

装饰IComponent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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的替代方法是什么:

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

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

1
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

然后,在创建LoggingDecorator类时,AutoFixture将重新使用相同的冻结的IComponent实例。

这里有2种方法:

将AutoFixture与自动模拟一起使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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);
}

在Auto Mocking和xUnit.net数据理论中使用AutoFixture:

1
2
3
4
5
6
7
8
9
10
11
12
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属性类定义为:

1
2
3
4
5
6
7
8
9
internal class TestConventionsAttribute : AutoDataAttribute
{
    internal TestConventionsAttribute()
        : base(
            new Fixture().Customize(
                new AutoMoqCustomization()))
    {
    }
}

注意:要编译并运行上述测试,请在LoggingDecorator中将IComponent的检查属性添加。