Mocking Data with Bogus

integerman

Matt Eland

Posted on September 18, 2019

Mocking Data with Bogus

Ever needed a bunch of random data for testing, UI prototyping, or even as part of an application? I did, and I found a good option to generate it for .NET applications.

Bogus is loosely based off of the Faker library for PHP and differs from testing libraries like AutoFixture by providing data that is closely analogous to what you might see in production.

If you're curious about AutoFixture, stay tuned as I plan on delving into the benefits that offers for testing purposes in a future article.

I've written previously on using Bogus to abstract away irrelevant values in unit testing, but I want to show you taking it one step further and using it to generate entire objects or collections of objects.

While this technique can help with unit test data generation, the code I'll be demoing here is actually intended as placeholder content while prototyping a user interface as well as for random content generation in a side project I'm working on.


In this tutorial, we're working under a scenario where I want to have a lot of randomly generated data representing the typical activities on a star ship (I said it was a fun side project, right?).

Specifically, I need to generate crew members, a list of ship's systems that can break, and problems associated with random systems. All of these objects are wrapped up in a single GameState class which is a result value that I return back to the caller of my .NET Core API.

My code to generate a new game state is very minimal:



public GameState BuildNewGameState(int id)
{
    var state = new GameState(id);
    state.Time = new GameTime(8, 4, 2422, 3, 20);

    state.GenerateCrewMembers(8)
         .GenerateSystems(10)
         .GenerateWorkItems(15);

    return state;
}


Enter fullscreen mode Exit fullscreen mode

The interesting bits here are a trio of extension methods I wrote against the GameState class that provide a fluent API for generating and configuring that object.

Let's start by taking a look at the GenerateCrewMembers method:



internal static GameState GenerateCrewMembers(this GameState state, int count)
{
    int nextId = state.NextCrewId;

    var faker = new Faker<CrewMember>()
        .RuleFor(c => c.Id, f => nextId++)
        .RuleFor(c => c.FirstName, f => f.Person.FirstName)
        .RuleFor(c => c.LastName, f => f.Person.LastName)
        .RuleFor(c => c.Department, f => f.PickRandom<Department>())
        .RuleFor(c => c.Rank, f => f.PickRandom<Rank>())
        .RuleFor(c => c.DaysInRank, f => f.Random.Int(0, 500));

    state.Add(faker.Generate(count));

    return state;
}


Enter fullscreen mode Exit fullscreen mode

The first thing to note is that this is a static method in a static class and prefixes the first parameter with the this keyword. This makes the GenerateCrewMembers method act like an extension method where the object it was invoked on is passed in as the first parameter. This paired with returning the same state object at the end is what creates a fluent API type of environment allowing chaining multiple method calls together.

The bulk of what this method is doing is generating a Faker instance and configuring the rules for how that should behave. Each RuleFor passes a pair of lambda expressions - one for configuring the property on the object being constructed and the other for using faker to generate a value for that property.

For example the line .RuleFor(c => c.FirstName, f => f.Person.FirstName) tells Bogus that when it sees the FirstName field on the object being configured, it should use the Bogus faker f's Person.FirstName property to generate a random first name from language-specific sets of data.

Department and Rank are both enums, so the f.PickRandom<Rank>() statement, for example, will grab a random Rank enum member and assign it to the Rank property on the object.

As we see with .RuleFor(c => c.Id, f => nextId++), you don't have to use Bogus for generating a value. In my case I chose to generate a sequential list of numerical Ids.

And finally, if you do want a random number value, as I do in DaysInRank, the statement .RuleFor(c => c.DaysInRank, f => f.Random.Int(0, 500)); will grab a random integer value between 0 and 500.

If you're a random number nerd and wondering what's going on under the covers for the number generation, Bogus is simply wrapping System.Random and calling in to an instance of that (which you can provide a seed for if you desire consistently random data).


Now that we have the crew generation, let's look at the ShipSystem generation code. This is trickier since ShipSystem has no parameter-less constructor and requires an int id to be passed in.

In this case, our code looks like this:



internal static GameState GenerateSystems(this GameState state, int count)
{
    int nextId = state.NextSystemId;

    var faker = new Faker<ShipSystem>()
        .CustomInstantiator(f => new ShipSystem(nextId++))
        .RuleFor(c => c.Name, f => f.Commerce.ProductName());

    state.Add(faker.Generate(count));

    return state;
}


Enter fullscreen mode Exit fullscreen mode

The first difference here is that the CustomInstantiator line allows you to configure how the instance is created. Thankfully the syntax for this is as simple as invoking a constructor, which I find preferable to trying to remember the parameter ordering and just passing in an array of objects as you would in using Reflection to instantiate objects, for example.

The second difference is that we're using the Faker instance's Commerce.ProductName property. This generates marvelous gems such as Rustic Concrete Pizza and Handmade Granite Chicken, among others. While not exactly star ship system names, it'll do for testing and prototyping.

An additional note here - if we wanted to make sure that every public property on the type being created was set, we could add in a call to .StrictMode(true) on the Faker<T> instance. This will raise exceptions if new properties are created that no rule was configured for.


Finally, let's get to configuring the WorkItems associated with the ship. This is an interesting collection because it interacts with both the Crew and the Systems collection in order to attribute work items to a crew member and problems with an impacted system.

The code is as follows:



internal static GameState GenerateWorkItems(this GameState state, int count)
{
    int nextId = 1;
    if (state.WorkItems.Any())
    {
        nextId = state.WorkItems.Max(c => c.Id) + 1;
    }

    var faker = new Faker<WorkItem>()
        .CustomInstantiator(f => new WorkItem(nextId++))
        .RuleFor(c => c.Title, f => f.Hacker.Phrase())
        .RuleFor(c => c.Department, f => f.PickRandom<Department>())
        .RuleFor(c => c.Status, f => WorkItemStatus.New)
        .RuleFor(c => c.Priority, f => f.PickRandom<Priority>())
        .RuleFor(c => c.WorkItemType, f => WorkItemType.Incident)
        .RuleFor(c => c.CreatedByCrewId, f => f.PickRandom(state.Crew).Id)
        .RuleFor(c => c.SystemId, f => f.PickRandom(state.Systems).Id)
        .RuleFor(c => c.AssignedCrewId, f => f.PickRandom(state.Crew.Select(c => c.Id))
                                              .OrNull(f, 0.85f));

    state.Add(faker.Generate(count));

    return state;
}


Enter fullscreen mode Exit fullscreen mode

Here everything is similar to what we've seen before with a few exceptions.

With CreatedByCrewId and SystemId, we're using PickRandom to pick a random member of a collection and then access a property on that entry.

AssignedCrewId works similarly, but I also want to support null values and expect them most of the time, so I select the Id property from each member of Crew and then call the Faker extension OrNull passing in the Faker instance and a percentage between 0 and 1 for how likely it should be that the value is null. Faker will then use this to vary up the values for assignment.

Finally, Title uses Hacker.Phrase for content generation. This is probably my favorite generator in Bogus that I've worked with and spits out lines that you might find on crime scene investigation TV shows. Here are a few of my favorites:

We need to copy the 1080p JSON firewall!

I'll navigate the virtual RAM port, that should port the RAM port!

I'll copy the haptic SSL system, that should system the SSL system!

Side note: I want a haptic SSL system if anyone has one to spare.

When I put it all together and run a scaled down version of the generation process, I get this lovely output:



[
  {
    "workItems":[
      {
        "title":"Try to synthesize the AI microchip, maybe it will synthesize the virtual microchip!",
        "id":1,
        "department":0,
        "assignedCrewId":null,
        "createdByCrewId":2,
        "systemId":1,
        "workItemType":0,
        "status":0,
        "priority":3
      }
    ],
    "crew":[
      {
        "firstName":"Rogelio",
        "lastName":"Morissette",
        "rank":13,
        "department":0,
        "daysInRank":253,
        "id":1
      },
      {
        "firstName":"Isabel",
        "lastName":"Langosh",
        "rank":12,
        "department":5,
        "daysInRank":184,
        "id":2
      }
    ],
    "systems":[
      {
        "id":1,
        "name":"Practical Plastic Bike"
      },
      {
        "id":2,
        "name":"Rustic Concrete Towels"
      },
      {
        "id":3,
        "name":"Sleek Concrete Computer"
      }
    ],
  }
]


Enter fullscreen mode Exit fullscreen mode

While this is largely random, it is useful data for prototyping and testing purposes and that helps me out quite a bit.

Bogus can do a lot more than this with its multiple language support, URL generation methods, database strings, colors, user reviews, mac addresses, E-Mail addresses, phone numbers, and even cultural specific things like US social security numbers, Canada SINs, and Great Britain vehicle registration plates.

Hopefully this shows you how a library like Bogus can speed up your workflow in prototyping or testing complex data.


Cover Photo by Eric Prouzet on Unsplash

💖 💪 🙅 🚩
integerman
Matt Eland

Posted on September 18, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

Mocking Data with Bogus
dotnet Mocking Data with Bogus

September 18, 2019