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.49k stars 480 forks source link

Determinism with Custom Datasets #544

Closed musictopia2 closed 2 months ago

musictopia2 commented 2 months ago

I used your example for extensions for food and drink. If you set the seed value to 20 for example, i was expecting to get the same results. Instead, i get different results. The problem only happens if you create extensions to have other datasets.

bchavez commented 2 months ago

If you're using Faker<T> method, then you'll need to extend Faker<T> to capture the internal Faker facade (aka FakerHub) so that setting the seed is flowed to the custom dataset as well.

Ultimately, every Faker facade has an IHasRandomizer interface that exposes the internal SeedNotifier that notifies child datasets when the Faker.Randomizer property has changed.

Basically, what you want to do with a custom dataset is to flow (register) your custom dataset with the internal Faker facade (aka Faker<T>.FakerHub) before using it; so that your custom dataset is notified when a seed value is changed or set.

For example,

void Main()
{
   var id = 0;
   var mealFaker = new ExtendedFaker<Meal>()
      .UseSeed(777)
      .RuleFor(m => m.Id,    f => id++)
      .RuleFor(m => m.Drink, f => f.Food().Drink())
      .RuleFor(m => m.Candy, f => f.Food().Candy());

   mealFaker.Generate(5).Dump();
   mealFaker.UseSeed(777); id = 0;
   mealFaker.Generate(5).Dump();
}

public class ExtendedFaker<T> : Faker<T> where T : class
{
   public ExtendedFaker() : base()
   {
      // registers Food() custom dataset with Faker facade (aka FakerHub):
      this.FakerHub.Food();
      // or call
      //ContextHelper.GetOrSet(this.FakerHub, () => new Food());
   }
}

public class Meal
{
   public int Id;
   public string Drink;
   public string Candy;
}

public static class ExtensionsForFood
{
   public static Food Food(this Faker faker)
   {
      return ContextHelper.GetOrSet(faker, () => new Food());
   }
}

public class Food : Bogus.DataSet
{
   private static readonly string[] Candies = { "Hard candy", "Taffy", "Chocolate bar", "Stick candy", "Jelly bean", "Mint", "Cotton candy", "Lollipop" };
   private static readonly string[] Drinks = { "Soda", "Water", "Beer", "Wine", "Coffee", "Lemonade", "Milk" };

   public string Candy() => this.Random.ArrayElement(Candies);
   public string Drink() => this.Random.ArrayElement(Drinks);
}

image

And using Faker facade only:

void Main()
{
   var faker = new Faker();
   faker.Food(); // registers Food() custom dataset with faker facade. Or similary call:
                 //ContextHelper.GetOrSet(faker, () => new Food());
   faker.Random = new Randomizer(777);
   Enumerable.Range(1,5).Select(_ => faker.Food().Candy()).Dump();
   faker.Random = new Randomizer(777);
   Enumerable.Range(1,5).Select(_ => faker.Food().Candy()).Dump();
}

image

Hope that helps.

Thanks, Brian