How to use Bogus Faker with initialization properties?

8.4k Views Asked by At

I have an immutable DTO that I'd like to fake with Bogus Faker (version 31.0.2) but the property with an overriding rule only returns what it was initialized with by the constructor:

Example DTO (real ones are more complex)

using Xunit;
using Bogus;

namespace SO.Tests
{
   class ClassWithInitialization
   {
      public ClassWithInitialization(string name)
      {
         this.Name = name
      }

      public string Name { get; }
   }

Example DTO Faker

   class FakeClassWithInitialization : Faker<ClassWithInitialization>
   {
      private FakeClassWithInitialization() { }

      public static CreateDefault()
      {
         return (FakeClassWithInitialization) new FakeClassWithInitialization()
            .CustomInstantiator(f => new ClassWithInitialization(null))
            .RuleFor(o => o.Name, f => f.Person.FullName);
      }

      public FakeClassWithInitialization WithName(string name)
      {
         RuleFor(o => o.Name, f => name);
         return this;
      }
   }

Example Tests

Both of the following tests fail as the Name property remains null as provided in the constructor.

   public class Tests
   {
      [Fact]
      public void TestClassWithInitialization()
      {
         var faker = FakeClassWithInitialization
            .CreateDefault();

         var testPoco = faker.Generate();

         Assert.False(string.IsNullOrEmpty(testPoco.Name)); #fails
      }

      [Fact]
      public void TestClassWithInitialization_with_overriding_rule()
      {
         var faker = FakeClassWithInitialization
            .CreateDefault()
            .WithName("John Smith");

         var testPoco = faker.Generate();

         Assert.AreEqual("John Smith", testPoco.Name); #fails
      }
   }
}

Although I could use Faker to generate random data for the constructor I would like to be able to use this fake instance to generate alternative versions, for instance, with a fixed Name as exampled by the second test above.

Why is this not working and are there any known workarounds?

Note: this is not the same as the question How can I use Bogus with private setters

3

There are 3 best solutions below

3
On

I just tried this and it seems to work :

class FakeClassWithInitialization : Faker<ClassWithInitialization>
{
    private FakeClassWithInitialization() { }

    public static FakeClassWithInitialization CreateDefault()
    {
        return (FakeClassWithInitialization) new FakeClassWithInitialization()
            .CustomInstantiator(f => new ClassWithInitialization(f.Person.FullName));
    }

}

I used directly the class constructor with the generator instead of using the generator with the property.

I also remove the WithName method that was not used.

Edit : Seems I misunderstood the question. I don't know much about Bogus. I thought you could use optional parameters in "CreateDefault" method but you told DTO was complex so... There will be too much parameters.

I think you can achieve what you want with the builder pattern :

public class Builder
{
    private string _name;

    public Builder WithName(string name)
    {
        _name = name;
        return this;
    }

    public ClassWithInitialization Build()
    {
        return new Faker<ClassWithInitialization>()
            .CustomInstantiator(f =>
                new ClassWithInitialization(
                    _name ?? f.Person.FullName
                ))
            .Generate();
    }
}

var faker = new Builder().WithName("Hello").Build();
var faker2 = new Builder().Build();

You can delete the FakeClassWithInitialization and replace it with a classic "Builder".

0
On

It is possible but I'd advise against it because the solution relies on .NET's Reflection.

There's a new Faker<T>(binder:...) binder constructor parameter. The IBinder interface is what Faker<T> uses to reflect over T to discover properties and fields that are settable. Whatever IBinder.GetMembers(Type t) returns is what Faker<> sees in T.

With this information, let's look at how the compiler generates an object with a public parameterized constructor and read-only property:

public class Foo
{
   public Foo(string name){
      this.Name = name;
   }
   public string Name { get; }
}

The C# compiler generates:

public class Foo
{
    // Fields
    [CompilerGenerated, DebuggerBrowsable((DebuggerBrowsableState) DebuggerBrowsableState.Never)]
    private readonly string <Name>k__BackingField;

    // Methods
    public Foo(string name)
    {
        this.<Name>k__BackingField = name;
    }

    // Properties
    public string Name => this.<Name>k__BackingField;
}

The storage for the Foo.Name property uses a backing field called Foo.<Name>k__BackingField. This backing field is what we need IBinder to hoist into Faker<>. The following BackingFieldBinder : IBinder does this:

public class BackingFieldBinder : IBinder
{
   public Dictionary<string, MemberInfo> GetMembers(Type t)
   {
      var availableFieldsForFakerT = new Dictionary<string, MemberInfo>();
      var bindingFlags = BindingFlags.NonPublic | BindingFlags.Instance;
      var allMembers = t.GetMembers(bindingFlags);     
      var allBackingFields = allMembers
                              .OfType<FieldInfo>()
                              .Where(fi => fi.IsPrivate && fi.IsInitOnly)
                              .Where(fi => fi.Name.EndsWith("__BackingField"))
                              .ToList();
      
      foreach( var backingField in allBackingFields){
         var fieldName = backingField.Name.Substring(1).Replace(">k__BackingField","");
         availableFieldsForFakerT.Add(fieldName, backingField);
      }
      return availableFieldsForFakerT;
   }
}

Customize the GetMembers() method above to suit your needs. You'll need to change the code if you want to include public fields or properties of T too.

The last problem we have to solve is creating an object without specifying constructor arguments. We can do this by using .GetUninitializedObject() from FormatterServices or RuntimeHelpers. To do this, we'll create an extension method that extends the Faker<T> API as shown below:

public static class MyExtensionsForFakerT
{
   public static Faker<T> SkipConstructor<T>(this Faker<T> fakerOfT) where T : class
   {
      return fakerOfT.CustomInstantiator( _ => FormatterServices.GetUninitializedObject(typeof(T)) as T);
   }
}

With these two components in place, we can finally write the following code:

void Main()
{
   var backingFieldBinder = new BackingFieldBinder();
   var fooFaker = new Faker<Foo>(binder: backingFieldBinder)
                      .SkipConstructor()
                      .RuleFor(f => f.Name, f => f.Name.FullName());
                      
   var foo = fooFaker.Generate();
   foo.Dump();
}

public class Foo
{
   public Foo(string name)
   {
      this.Name = name;
   }
   public string Name {get;}
}

Results

You can find a full working example here. Additionally, you may find other solutions in Issue 213 helpful.

0
On

This seemed pretty easy to me, not sure if this has been added since the question was asked:

private readonly static Faker<Address> addressFaker = new Faker<Address>()
    .CustomInstantiator(f => new Address(f.Person.FullName, f.Address.StreetAddress(), f.Country().UnitedKingdom().PostCode(), f.Company.CompanyName(), f.Address.Locale, f.Address.City(), f.Address.County()));