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.62k stars 491 forks source link

Passing Parameters for Rules #460

Closed shawnwildermuth closed 1 year ago

shawnwildermuth commented 1 year ago

Please describe why you are requesting a feature

I'd like to be able to pass a parameter to the GenerateXXX() methods so that I can a Rule can use that parameter for initialization

Please provide a code example of what you are trying to achieve

var orderFaker = new Faker<Order>()
  .RuleFor(o => o.OrderId, f => f.IndexFaker)
  .RuleFor(o => o.CustomerId, (f,b,p) => p); //  third parameter to be the passed in value

var custFaker = new Faker<Customer>()
  .RuleFor(c => c.Id, f => f.IndexFaker)  
  .RuleFor(c => c.Name, f => f.Name.FullName())
  .RuleFor(c => c.Orders, f => orderFaker.GenerateWithParam(10, null, f.IndexFaker)); // Passing in the id of the customer to the order faker

Please answer any or all of the questions below

AFAIK

Post processing the generated data

No.

If the feature request is approved, would you be willing to submit a PR?

Yes

bchavez commented 1 year ago

Hi @shawnwildermuth,

Thank you for posting.

Every Faker<T> internally contains a Faker (non-generic) object; which is used for the f => parameter. The Faker f has an IHasContext interface + .Context property that can be leveraged to flow context. This is a somewhat hidden API, but it's doable with the current release.

Here is one approach (of probably many) shown below: (Tested with LINQPad 7 and Bogus 34.0.2)

void Main()
{
   var orderFaker = new Faker<Order>()
     .RuleFor(o => o.OrderId, f => f.IndexFaker)
     .RuleFor(o => o.CustomerId, (f,o) => f.FromContext(nameof(o.CustomerId)));

   var custFaker = new Faker<Customer>()
     .RuleFor(c => c.Id, f => f.IndexFaker)
     .RuleFor(c => c.Name, f => f.Name.FullName())
     .RuleFor(c => c.Orders, f => orderFaker.WithContext(nameof(Order.CustomerId), f.IndexFaker).Generate(3));

     custFaker.Generate(3).Dump();
}

public static class ExtensionsForIssue460
{
   public static object FromContext(this Faker faker, string key)
   {
      var fakerContext = faker as IHasContext;
      return fakerContext.Context[key];
   }

   public static Faker<T> WithContext<T>(this Faker<T> fakerT, string propertyName, object value) where T : class
   {
      var internals = fakerT as IFakerTInternal;
      var faker = internals.FakerHub;
      var fakerContext = faker as IHasContext;
      fakerContext.Context[propertyName] = value;
      return fakerT;
   }
}

public class Order
{
   public int OrderId { get; set; }
   public int CustomerId { get; set; }

}
public class Customer
{
   public int Id { get; set; }
   public string Name { get; set; }
   public List<Order> Orders { get; set; }
}

image

I'm not super convinced yet that introducing an overloaded .GenerateWithParam(...) is exactly the best approach for this kind of context flow. Introducing a new public API like .GenerateWithParam(...) requires passing a very strict and high bar for Bogus; and my gut feeling is that I'm just not there yet for this kind of problem.

Feel free to continue the conversation or close the issue if you find the solution above satisfactory.

Thanks, Brian Chavez

shawnwildermuth commented 1 year ago

That's awesome! Thanks, this using Context makes more sense than the overload. Just glad there is a way to do this.