如何在AutoFixture中更新夹具的自定义

时间:2018-09-18 04:42:19

标签: unit-testing autofixture

Testbase类中,它像这样自定义MyClass

fixture.Customize<MyClass>(c => c.Without(r => r.Foo));

并且在Testbase的子类中具有

fixture.Customize<MyClass>(c => c.Without(r => r.Bar));

问题是我想在MyClass的创建过程中添加更多的自定义内容,但似乎最后一个获胜/取代了第一个,因此它仍然设置MyClass.Foo。 那么,如何更新自定义而不是覆盖自定义?

2 个答案:

答案 0 :(得分:1)

简短的答案是您不能这样做。 Customize<> API始终会附加一个新的自定义项,因此前一个是被覆盖的-这是设计使然。

作为一种可能的解决方法,您可以使用一些不同的语法来省略单个属性:

fixture.Customizations.Add(
    new Omitter(
        new EqualRequestSpecification(
            typeof(MyClass).GetProperty(nameof(MyClass.Foo)))));

这样,您可以追加自定义项并在以后扩展例外列表:

fixture.Customizations.Add(
    new Omitter(
        new EqualRequestSpecification(
            typeof(MyClass).GetProperty(nameof(MyClass.Bar)))));

请注意,如果您具有继承,则此方法会影响整个类的层次结构。因此,如果省略BaseClass.Foo,则还将在每个子类中省略Foo属性。

答案 1 :(得分:0)

有可能,但据我所知,您必须编写自己的治具才能传递给ICustomization的Customize方法。

这是我制作的一个示例:https://github.com/LethargicDeveloper/Lift.AutoFixture

我创建了一个扩展方法“ Compose()”,该方法需要进行ICustomization。它将一个特殊的灯具注入到您定义的自定义类中,并在将所有自定义添加到原始灯具的“自定义”集合中之前将所有自定义组合。

下面是完成大部分工作的代码段。 灯具具有一个定制集合,其中包含将要应用的所有定制。 Customization集合是List ,据我所知,没有简单的方法来知道每个构建器将创建什么类型。因此,在我的新设备中,我保留了Dictionary中的类型信息,并等到您调用BuildFixture之后,才将实际的自定义项添加到“自定义项”列表中。

键在Customize 方法中。这就是您用来创建自定义项的设备上的方法,因此我们将其覆盖。每次调用它时,都会将当前自定义项从给定类型的“构建器”列表中拉出,并由新的自定义项构成,然后再重新设置到“构建器”列表中。

import (
    "fmt"
    "net/http"
    "sync"
    "time"

    "golang.org/x/net/context"
)

func main() {

    var wg sync.WaitGroup
    wg.Add(10)
    c := make(chan string, 100)
    ctx := context.Background()

    ctx, cancel := context.WithTimeout(ctx, 5*time.Second)

    defer cancel()
    for i := 1; i <= 10; i++ {
        go SendHttpRequest(ctx, c, &wg)
    }

    for v := range c {
        fmt.Println(v)
    }

    wg.Wait()

}

func SendHttpRequest(ctx context.Context, c chan string, wg *sync.WaitGroup) {

    //defer wg.Done()
    client := http.Client{}
    req, err := http.NewRequest("POST", "https://jsonplaceholder.typicode.com/posts/1", nil)
    if err != nil {
        panic(err)
    }
    req.WithContext(ctx)

    res, _ := client.Do(req)

    select {
    case <-time.After(1 * time.Microsecond):
        c <- res.Status
    case <-ctx.Done():
        c <- "599 ToLong"
    }
    if res != nil {
        defer res.Body.Close()
    }
    //close(c)
    defer wg.Done()
}

使用您的示例,您可以这样编写:

internal class ComposableCustomizationBuilder : IFixture
{
    private readonly IFixture fixture;

    public ComposableCustomizationBuilder(IFixture fixture)
    {
        this.fixture = fixture;
        this.Builders = new Dictionary<Type, object>();
    }

    // most of the IFixture methods have been removed for brevity since they just return the this.fixture implementation.

    internal Dictionary<Type, object> Builders { get; }

    public IFixture Customize(ICustomization customization)
    {
        customization.Customize(this);
        return this;
    }

    public void Customize<T>(Func<ICustomizationComposer<T>, ISpecimenBuilder> composerTransformation)
    {
        if (!this.Builders.TryGetValue(typeof(T), out object builder))
        {
            var specimenBuilder = composerTransformation(SpecimenBuilderNodeFactory.CreateComposer<T>().WithAutoProperties(true));
            this.Builders.Add(typeof(T), new NodeComposer<T>(specimenBuilder));
            return;
        }

        this.Builders[typeof(T)] = composerTransformation(builder as ICustomizationComposer<T>);
    }

    internal IFixture BuildFixture()
    {
        if (this.fixture is ComposableCustomizationBuilder)
        {
            return this;
        }

        foreach (var builder in this.Builders)
        {
            var specimenBuilder = builder.Value as ISpecimenBuilder;
            this.fixture.Customizations.Add(specimenBuilder);
        }

        return this.fixture;
    }
}