Reputation: 240
I want to leverage xUnit theories together with AutoFixture to generate anonymous objects but with some explicit properties.
That's what i have now:
System under test
public class Task
{
public TaskState TaskState { get; set;}
public int Progress { get; set; }
}
Generic customization
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);
}
And attribute to use it
[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);
}
}
So, if I use it to specify only state like there, it works well and builds anonymous task
[Theory, AutoMoqData]
public void TestSomething([AutoTask(TaskState.InProgress)]Task task)
{...}
But if i want to setup both state and progress, it set up only second property for some reason, although both 'Do' delegates are called, but in the second call it receives task with default state again.
[Theory, AutoMoqData]
public void TestSomething([AutoTask(TaskState.InProgress, 50)]Task task)
{...}
I suspect that CompositeCustomization with multiple 'Do'-based customizations is the reason but don't get why.
Upvotes: 2
Views: 2105
Reputation: 233150
Why don't you just do the following?
[Theory, AutoMoqData]
public void TestSomething(Task task)
{
task.TaskState = TaskState.InProgress;
// The rest of the test...
}
or this?
[Theory, AutoMoqData]
public void TestSomething(Task task)
{
task.TaskState = TaskState.InProgress;
task.Progress = 50;
// The rest of the test...
}
This is much simpler, and type-safe too...
Upvotes: 4
Reputation: 240
It does not work because each next Type customization via Customize completely overrides previous one (Thanks to Mark for explanation).
So i changed customization to configure type rather than its property:
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;
}
}
And it can be used in attribute definition like 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;
}
}
Upvotes: 1