使用AutoFixture自定义显式设置样本的多个属性

时间:2015-05-27 09:38:23

标签: c# xunit autofixture

我想利用xUnit理论和AutoFixture来生成匿名对象,但具有一些显式属性。

这就是我现在所拥有的:

受测试系统

public class Task
{
    public TaskState TaskState { get; set;}
    public int Progress { get; set; }
}

通用定制

public class PropertyCustomization<T> : ICustomization
{
    private readonly string propertyName;

    private readonly object value;

    public PropertyCustomization(string propertyName, object value)
    {
        this.propertyName = propertyName;
        this.value = value;
    }

    public void Customize(IFixture fixture)
    {
        fixture.Customize<T>(cmp => cmp.Do(obj => obj.SetProperty(this.propertyName, this.value)));
    }
}

...

public static void SetProperty(this object instance, string propertyName, object value)
{
    var propertyInfo = instance.GetType().GetProperty(propertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
    propertyInfo.SetValue(instance, value);
}

并使用它的属性

[AttributeUsage(AttributeTargets.Parameter)]
public sealed class AutoTaskAttribute : CustomizeAttribute
{
    private readonly int progress;

    private readonly TaskState taskState;

    public AutoTaskAttribute(TaskState taskState, int progress = -1)
    {
        this.taskState = taskState;
        this.progress = progress;
    }

    public override ICustomization GetCustomization(ParameterInfo parameter)
    {
        if (parameter == null)
        {
            throw new ArgumentNullException("parameter");
        }

        var result = new List<ICustomization> { new PropertyCustomization<Task>("TaskState", this.taskState) };

        if (this.progress > -1)
        {
            result.Add(new PropertyCustomization<Task>("Progress", this.progress));
        }

        return new CompositeCustomization(result);
    }
}

因此,如果我使用它来仅指定那里的状态,那么它运行良好并构建匿名任务

[Theory, AutoMoqData]
public void TestSomething([AutoTask(TaskState.InProgress)]Task task)
{...}

但是如果我想设置状态和进度,它出于某种原因只设置了第二个属性,虽然两个'Do'委托都被调用,但在第二个调用中它再次接收具有默认状态的任务。

[Theory, AutoMoqData]
public void TestSomething([AutoTask(TaskState.InProgress, 50)]Task task)
{...}

我怀疑具有多个“基于”自定义的CompositeCustomization是原因,但不明白为什么。

2 个答案:

答案 0 :(得分:4)

你为什么不这样做?

[Theory, AutoMoqData]
public void TestSomething(Task task)
{
    task.TaskState = TaskState.InProgress;

    // The rest of the test...
}

还是这个?

[Theory, AutoMoqData]
public void TestSomething(Task task)
{
    task.TaskState = TaskState.InProgress;
    task.Progress = 50;

    // The rest of the test...
}

这样更简单,也是类型安全的......

答案 1 :(得分:1)

它不起作用,因为通过Customize的每个下一个Type自定义完全覆盖了前一个(感谢Mark提供解释)。

所以我改变了自定义来配置类型而不是它的属性:

public class TypeCustomization<T> : ICustomization
{
    private List<Action<T>> actions;

    public TypeCustomization()
    {
        this.actions = new List<Action<T>>();
    }

    public void Customize(IFixture fixture)
    {
        fixture.Customize<T>(
            cmp =>
                {
                    return this.actions.Aggregate<Action<T>, IPostprocessComposer<T>>(cmp, (current, next) => current.Do(next));
                });
    }

    public TypeCustomization<T> With(string propertyName, object value)
    {
        this.actions.Add(obj => obj.SetProperty(propertyName, value));
        return this;
    }
}

它可以在这样的属性定义中使用:

[AttributeUsage(AttributeTargets.Parameter)]
public sealed class AutoTaskAttribute : CustomizeAttribute
{
    private readonly int progress;

    private readonly TaskState taskState;

    public AutoTaskAttribute(TaskState taskState, int progress = -1)
    {
        this.taskState = taskState;
        this.progress = progress;
    }

    public override ICustomization GetCustomization(ParameterInfo parameter)
    {
        var customization = new TypeCustomization<Task>().With("TaskState", this.taskState);

        if (this.progress > -1)
        {
            customization.With("Progress", this.progress);
        }

        return customization;
    }
}