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

Setter Via explicite interface Property #467

Closed SerratedSharp closed 1 year ago

SerratedSharp commented 1 year ago

I have a scenario where I am implementing a faker over a concrete type, but one of the rules references an interface property, and it doesn't seem to be actually setting the value. If I don't use explicit interface implementation it works. I suspect somewhere there's reflection code that's using a flag to get properties and it doesn't pick up explicitly implemented interface members.

The purpose of this is to allow me to handle a large number of code tables with a common set of generically typed code as long as the entity class implements ICode. I implement ICode explicitly so that it doesn't confuse programmers using the concrete type. Of course the below is a simplification for reproduction. In practice I'm using Get() where T:class,ICode, which allows me to still use the faker on the concrete type, but access members of ICode generically.

It otherwise works fine until I change the ICode implementation from public to explicit.

Works, interface property and Rule referencing (x as ICode).Name works fine:

public interface ICode {
     string Name {get;set;}
}

public class SomeCode:ICode {
     public SomeName {get;set;}
     public Name { get=> SomeName; set=>SomeName = value; }
}

var nameIsThere = new Fake<SomeCode>() 
   .RuleFor(x => (x as ICode).Name, v=> "Blah")
   .Generate(1);

Not working

public interface ICode {
     string Name {get;set;}
}

public class SomeCode:ICode {
     public SomeName {get;set;}
     ICode.Name { get=> SomeName; set=>SomeName = value; } // implement explicitly breaks the faker
}

var nameIsNull = new Fake<SomeCode>() 
   .RuleFor(x => (x as ICode).Name, v=> "Blah")
   .Generate(1);
SerratedSharp commented 1 year ago

I did some experimenting and by implementing a custom binder inheriting from the Bogus.Binder and checking what base.GetMembers returned, I could see the name format of the explicit interface properties.

I then removed the custom binder, and changed to this, which is now working. Basically need to use a fully qualified name for the interface's property.

var nameIsBetterNow = new Fake() .RuleFor("MyNamespace.MoreNamespace.ICode.Name", v=> "Blah") .Generate(1);

bchavez commented 1 year ago

Yep, you got it! Nice work @SerratedSharp.

Creating a CustomBinder of type Bogus.Binder or Bogus.IBinder is the way to go if you want to control what Faker<T> sees & uses for its .RuleFor properties.

If you wanted to remove the string, you can probably build on what you've got now with some C# extension methods .RuleForICodeName(v => "Blah").