bchavez / Bogus

:card_index: A simple fake data generator for C#, F#, and VB.NET. Based on and ported from the famed faker.js.
Other
8.68k stars 495 forks source link

Generate with rules #358

Closed Eonasdan closed 3 years ago

Eonasdan commented 3 years ago

Version Information

Software Version(s)
Bogus NuGet Package 31.0.3
.NET Full Framework? 5

What locale are you using with Bogus?

en

What's the problem?

Hello, thanks for the great package. I'm building a fake api so we can test fetching from a third party prod api that doesn't have a sandbox. I've converted their json to c# classes and I'm using the same with Bogus (and autobogus) so there's "real" stuff in there.

One thing that I'm trying to make work is that in the complex structure there are for instance two places where the "first name" is. Ideally, this would be the same when creating the fakes. I have created a : Faker<T> that in the constructor uses var p = new Bogus.Person() and can be used as a.y.FirstName = person.Firstname and a.x.FirstName = person.Firstname. Unfortunately, since it's on the ctro calling Generate(5) results in 5 objects with the same person. Is there anything I can do to make these rules work this way?

Do you have sample code to show what you're trying to do?

public class Person {
    public string FirstName {get;set;}
    public string LastName {get;set;}
    //etc
}

public class Extended
{
    public string LegalFirstName { get; set; }
    public string LegalLastName {get;set;}
    //etc
}

public class Foo {
    public Person Person {get;set;}
    public Extended Extended {get;set;}
}

public class FooFaker : Faker<Foo> {
    public FooFaker(string firstName = "")
    {
        var person = new Bogus.Person();
        if (!string.IsNullOrEmpty(firstName))
        {
            person.FirstName = firstName;
        };

        RuleFor(x => x.Person, () => new Person
        {
            FirstName = person.FirstName,
            LastName = person.LastName
        });

        RuleFor(x => x.Extended, () => new Extended
        {
            LegalFirstName = person.FirstName,
            LegalLastName = person.LastName
        });
    }
}

public class Program {
    private static void Main(string[] args){
        var faker = new FooFaker();
        var result = faker.Generate(2);
        result.Dump();

        faker = new FooFaker("from api search");
        result = faker.Generate(2);
        result.Dump();
    }
}

I've got a sample as a LinqPad file. You'll have to rename it to .linq. bogus rule for.txt

bchavez commented 3 years ago

Hi @Eonasdan, Thank you so much for the working LinqPad example. This really helps answering questions. :)

I think what you want to do is "functionalize" how the first name is picked in the .RuleFor( _ , f => work ) so that the variable closure firstName isn't captured and persisted in the lambda. Here's what I did:

void Main()
{
   var faker = new FooFaker();
   var result = faker.Generate(2);
   result.Dump();

   faker = new FooFaker("from api search");
   result = faker.Generate(2);
   result.Dump();
}

public class FooFaker : Faker<Foo>
{
   public FooFaker(string firstName = "")
   {
      string PickFirstName(Faker f){
         if (!string.IsNullOrEmpty(firstName))
            return firstName;
         else
            return f.Person.FirstName;
      }

      RuleFor(x => x.Person, f => new Person
                                      {
                                         FirstName = PickFirstName(f),
                                         LastName = f.Person.LastName 
                                      }
              );
      RuleFor(x => x.Extended, f => new Extended
                                        {
                                           LegalFirstName = PickFirstName(f),
                                           LegalLastName = f.Person.LastName 
                                        }
              );
   }
}

public class Person {
   public string FirstName { get; set; }
   public string LastName { get; set; }
}

public class Extended
{
   public string LegalFirstName { get; set; }
   public string LegalLastName { get; set; }
}

public class Foo
{
   public Person Person { get; set; }
   public Extended Extended { get; set; }
}

The results are what I think you're asking for:

image

Let me know if that works for you. Also, feel free to carry on the conversation after I close the issue. :+1:

Hope that helps, Brian

Eonasdan commented 3 years ago

Thanks for the help. I had tried something similar but for some reason my brain didn't want to put all the pieces together lol.

I ended up creating a BaseFaker class that has something like public string FirstName(Faker f) => !string.IsNullOrEmpty(Where.FirstName) ? Where.FirstName : f.Person.FirstName; since I have to do this in more then one controller. The only thing that didn't work was a middle name property. That's not a huge deal but thought I'd ask:

 public string MiddleName(Faker f) =>
            !string.IsNullOrEmpty(Where.MiddleName) ? Where.MiddleName : f.Name.FirstName(f.Person.Gender);

This generates two different names in Person and Extended. I was looking trying to create an extension method or a PersonWithMiddle : Person

Thanks again.

bchavez commented 3 years ago

Hi @Eonasdan , I'm having a bit of a hard time understanding the problem without a complete picture of the code. If you'd like more help, post a full example and I'll be happy to take a 2nd look. Thanks, Brian.