5
\$\begingroup\$

I'm using the Bogus library to mock up some data for testing.

This is my main method demonstrating how the faking service should be used:

class Program
{
    static void Main(string[] args)
    {
        IFakerService r = new FakerService();

        var dogs = r.Generate<Dog>(10);
        var customers = r.Generate<Customer>(10);
        var cats = r.Generate<Cat>(10);
    }
}

A Dog class looks like:

public class Dog
{
    public string Name { get; set; }
    public bool IsGoodBoy => true;
}

My interface simply defines the only method the service should expose:

public interface IFakerService
{
    IEnumerable<T> Generate<T>(int count) where T : class;
}

And the service itself:

public class FakerService : IFakerService
{
    // Can we do better than <Type, object> ?
    private Dictionary<Type, object> _fakers;

    public FakerService()
    {
        _fakers = InitialiseFakers();
    }

    public IEnumerable<T> Generate<T>(int count) where T : class
    {
        var faker = GetFaker<T>();
        return faker.Generate(count);
    }

    // Types which can be faked are registered here
    private Dictionary<Type, object> InitialiseFakers()
    {
        return new Dictionary<Type, object>()
        {
            {typeof(Dog), new DogFaker().GetFaker()},
            {typeof(Cat), new CatFaker().GetFaker()},
            {typeof(Customer), new CustomerFaker().GetFaker()}
        };
    }

    private Faker<T> GetFaker<T>() where T : class
    {
        if (!_fakers.ContainsKey(typeof(T)))
        {
            throw new ArgumentException($"Type: {typeof(T).FullName} not registered with FakerService");
        }
        var faker = (Faker<T>)_fakers[typeof(T)];
        return faker;
    }
}

The interface for Faker objects:

public interface IFaker<T> where T : class
{
    Faker<T> GetFaker();
}

And one of the implementations:

public class DogFaker : IFaker<Dog>
{
    public Faker<Dog> GetFaker()
    {
        return new Faker<Dog>()
            .RuleFor(dog => dog.Name, f => f.Name.FirstName(Name.Gender.Male))
            .RuleFor(dog => dog.IsGoodBoy, f => f.Random.Bool());
    }
}

Overall I'm quite happy with how it looks - the parts that I was wondering if I could improve on are:

private Dictionary<Type, object> _fakers;

...

return new Dictionary<Type, object>()
{
    {typeof(Dog), new DogFaker().GetFaker()}
}

...

var faker = (Faker<T>)_fakers[typeof(T)];

Any advice is appreciated!

\$\endgroup\$
  • 3
    \$\begingroup\$ IsGoodBoy should ALWAYS return true >:( \$\endgroup\$ – Steven Eccles Nov 1 '18 at 14:08
  • \$\begingroup\$ This looks like a cache. Are you building one? What problem does this solve? Currently I think it's easier to just call the Faker APIs when you need them. I'm not sure how this service helps. \$\endgroup\$ – t3chb0t Nov 1 '18 at 15:45
  • \$\begingroup\$ We want to use faked objects in different areas across our solution - in tests, seeding etc. \$\endgroup\$ – TomSelleck Nov 1 '18 at 15:49
  • \$\begingroup\$ This approach stops us from needing to define the setup rules repeatedly or the ability to add parameters to the fakers to come up with a particular set of mocked parameters \$\endgroup\$ – TomSelleck Nov 1 '18 at 15:51
  • 1
    \$\begingroup\$ @StevenEccles I guess it's just an example ;-] \$\endgroup\$ – t3chb0t Nov 2 '18 at 7:14
4
\$\begingroup\$
public interface IFaker<T> where T : class
{
    Faker<T> GetFaker();
}

I don't think you need this interface and the additional call to GetFaker. Instead you can derive your class from the Faker<T> and use the constructor to set everything up:

public class DogFaker : Faker<Dog>
{
    public DogFaker()
    {
        RuleFor(dog => dog.Name, f => f.Name.FirstName(Name.Gender.Male));
        RuleFor(dog => dog.IsGoodBoy, f => f.Random.Bool());
    }   
}

private Faker<T> GetFaker<T>() where T : class
{
  if (!_fakers.ContainsKey(typeof(T)))
  {
      throw new ArgumentException($"Type: {typeof(T).FullName} not registered with FakerService");
  }
  var faker = (Faker<T>)_fakers[typeof(T)];
  return faker;
}

In this case it would be much clener to query the dictionary only once with TryGetValue if you are going to use the value anyway rather then doing it twice, once with ContainsKey and then with the [] indexer. At the same time you can tun it into a ternary operator ?::

private Faker<T> GetFaker<T>() where T : class
{
    return
        _fakers.TryGetValue(typeof(T), out var faker)
            ? (Faker<T>)faker
            : throw new ArgumentException($"Type: {typeof(T).FullName} not registered with FakerService");
}

Since ´InitialiseFakers´ does not require any instance data I suggest making it static. I like the way you created this initializer. Instead of letting it set the _fakers dictionary you return it as a result and assign it in the construtor. This way it's much cleaner and all the initialization-assignment stays inside the construtor so a big plus for that.


// Can we do better than <Type, object> ?
private Dictionary<Type, object> _fakers;

I'm afraid we cannot because Faker<T> isn't derived from any other collection-friendly interface. All you have is:

public class Faker<T> : IFakerTInternal, ILocaleAware, IRuleSet<T> where T : class

and this is not very useful so object is the only possible generalisation in this case. But this isn't that bad. It's an object so it doesn't cost you a penny to cast it. But sure, some general interface would be nice.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.