Reputation: 174299
I have the following types:
public enum Status
{
Online,
Offline
}
public class User
{
private readonly Status _status;
public User(Status status) { _status = status; }
public Status Status {get {return _status; }}
public string Name {get;set;}
}
Now, when executing fixture.CreateMany<User>
I want AutoFixture to return two Users
, one per status. All other properties - like Name
- should be filled with anonymous data.
Question:
How to configure AutoFixture to do this?
I tried the following this:
Register collection that news up the User
object:
fixture.Register(
() => Enum.GetValues(typeof(Status)).Cast<Status>().Select(s =>
new User(s)));
The problem with this approach is that AutoFixture doesn't fill the other properties like Name
Customize User
to use a factory and register a collection that uses fixture.Create
:
f.Customize<User>(c => c.FromFactory((Status s) => new User(s)));
f.Register(() =>
Enum.GetValues(typeof(Status))
.Cast<Status>()
.Select(s => (User)f.Create(new SeededRequest(typeof(User), s),
new SpecimenContext(f))));
That didn't work either. The seed isn't being used.
Upvotes: 9
Views: 9428
Reputation: 411
Current way of doing this with AutoFixture 4.17.0
fixture
.Build<User>()
.With(u => u.Status, Status.Offline)
.CreateMany(5)
.ToList();
Upvotes: 0
Reputation: 486
I know it is already answered and the Generator was a very interesting finding. I think there is a much simpler approach for this problem.
var numberOfEnumValues = Enum.GetValues(typeof(Status)).Length;
var users = fixture.CreateMany<User>(numberOfEnumValues);
In case the constructor is more complicated, with multiple Status values, or the model has property setters of Status type. Then you generally have a problem, and the generator might blow as well.
Say that:
public class SuperUser : User
{
public SuperUser(Status status, Status shownStatus): base(status)
{
}
}
Then this will never be evaluated:
var users = fixture.Create<Generator<SuperUser>>();
var offlineUser = users.Where(u => u.Status == Status.Offline).First();
Upvotes: 2
Reputation: 174299
Based on Mark's answer, this is what I am using now:
fixture.Customize<User>(c => c.Without(x => x.Status));
fixture.Customize<IEnumerable<User>>(
c =>
c.FromFactory(
() => Enum.GetValues(typeof(Status)).Cast<Status>()
.Select(s => users.First(u => u.Status == s))));
fixture.Create<IEnumerable<User>>(); // returns two Users
Upvotes: 4
Reputation: 233135
You could do this:
var users = new Fixture().Create<Generator<User>>();
var onlineUser = users.Where(u => u.Status == Status.Online).First();
var offlineUser = users.Where(u => u.Status == Status.Offline).First();
If you're using AutoFixture.Xunit, the declarative equivalent is:
[Theory, AutoData]
public void CreateOneOfEachDeclaratively(Generator<User> users)
{
var onlineUser = users.Where(u => u.Status == Status.Online).First();
var offlineUser = users.Where(u => u.Status == Status.Offline).First();
// Use onlineUser and offlineUser here...
}
Upvotes: 6
Reputation: 11201
You may declare and use a customization, e.g. StatusGenerator
:
var fixture = new Fixture();
fixture.RepeatCount = 2;
fixture.Customizations.Add(new StatusGenerator());
var result = fixture.CreateMany<User>();
A hypothetical implementation of the StatusGenerator
could be the following:
internal class StatusGenerator : ISpecimenBuilder
{
private readonly Status[] values;
private int i;
internal StatusGenerator()
{
this.values =
Enum.GetValues(typeof(Status)).Cast<Status>().ToArray();
}
public object Create(object request, ISpecimenContext context)
{
var pi = request as ParameterInfo;
if (pi == null || !pi.ParameterType.IsEnum)
return new NoSpecimen(request);
return this.values[i == this.values.Length - 1 ? i = 0 : ++i];
}
}
Upvotes: 6