9

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:

  1. 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

  2. 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.

Daniel Hilgarth
  • 171,043
  • 40
  • 335
  • 443

5 Answers5

6

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...
}
Mark Seemann
  • 225,310
  • 48
  • 427
  • 736
  • Thanks for your answer. Unfortunatelly, that doesn't work if the `Status` property isn't `readonly`. `First` "never" returns (I waited at least 30 seconds). – Daniel Hilgarth Jun 24 '13 at 16:50
  • The `readonly` keyword has nothing to do with it. I just removed the `readonly` keyword from my repro, and it didn't change the result. – Mark Seemann Jun 24 '13 at 17:21
  • I am not talking about the backing field not being readonly. I am talking about the *property* not being readonly, i.e. `public Status Status { get; set; }`. – Daniel Hilgarth Jun 24 '13 at 17:31
  • 2
    OK, I can reproduce this if I make the property writable, and *keep* the constructor argument. What you're seeing here is a resonance effect, because each instance receives *exactly* two instances of `Status`, and because there's only two values of `Status`, it'll always end up being the same because enum values are (still) generated in a deterministic round-robin fashion. Perhaps we should change the default behaviour of AutoFixture, but that would be a breaking change... If you can make the property read-only, remove the constructor argument, or use a larger enum, you should be good. – Mark Seemann Jun 24 '13 at 17:51
  • Thanks for the explanation - that makes sense. Another way to "fix" it is by customizing `User` with `c => c.Without(x => x.Status)`. That's the cleanest solution IMO, because it doesn't touch the `User` class itself which (hopefully) has good reasons to be designed the way it is. (Please note: `User` was just an example for the sake of this question. A discussion whether or not this particular design is good or not is not necessary). – Daniel Hilgarth Jun 25 '13 at 08:02
  • Now that I think about it, if the properties are writable, a much easier solution is to just create two instances of `User` and then assign the values subsequently. No AutoFixture tweaking would be required at all. – Mark Seemann Jun 25 '13 at 08:55
  • I don't understand that comment. Can you elaborate? Please keep in mind that I don't care for the distinct instances of `User`, I only care for the `IEnumerable`. And it should contain one user per enum value, *no matter how many enum values there are*. – Daniel Hilgarth Jun 25 '13 at 08:57
  • `var users = fixture.Create>().Take(2).ToList();` gives you two instances of `User`. If `Status` is writable, you can just go `users[0].Status = Status.Online;` and `users[1].Status = Status.Offline;` – Mark Seemann Jun 25 '13 at 15:36
  • Yeah, but that's what I want to avoid doing in the first place, because it would mean that I would have to do it in every single unit test that requires an `IEnumerable`. See [my answer](http://stackoverflow.com/a/17301718/572644) to see what I am doing now to avoid this. – Daniel Hilgarth Jun 25 '13 at 15:40
  • Something like that was going to be my next suggestion :) – Mark Seemann Jun 25 '13 at 20:22
5

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];
    }
}
Nikos Baxevanis
  • 10,868
  • 2
  • 46
  • 80
  • Nice, thanks. Would it be possible without setting `RepeatCount`? As I understand, this affects *all* calls to `CreateMany`, not just those for `User`... – Daniel Hilgarth Jun 14 '13 at 23:39
4

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
Community
  • 1
  • 1
Daniel Hilgarth
  • 171,043
  • 40
  • 335
  • 443
2

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();
0

Current way of doing this with AutoFixture 4.17.0

fixture
.Build<User>()
.With(u => u.Status, Status.Offline)
.CreateMany(5)
.ToList();
freshbm
  • 5,540
  • 5
  • 46
  • 75
Benj
  • 405
  • 4
  • 12
  • Are you sure? To me, it looks like it will create 5 users all with the Status being Offline. That's not what I asked 9 years ago – Daniel Hilgarth Dec 10 '22 at 11:55