Generating Realistic Test Data with Bogus in .NET

Hard-coded test data like "Test User" and "[email protected]" is easy to write but introduces subtle problems. It doesn't exercise edge cases in string handling, it makes tests harder to read when multiple entities share identical dummy values, and it can mask bugs that only appear with realistic data. Bogus is a .NET port of the popular faker.js library that generates convincing fake data — names, addresses, emails, phone numbers, and much more — with support for dozens of locales.

Installation

Example.cs
dotnet add package Bogus

Basic Usage with Faker

The Faker class provides access to data generators organised by category:

Example.cs
var faker = new Faker("en_GB");

var name = faker.Name.FullName();           // "Oliver Thompson"
var email = faker.Internet.Email();         // "[email protected]"
var phone = faker.Phone.PhoneNumber();      // "0118 496 0832"
var address = faker.Address.FullAddress();  // "42 High Street, Bristol, BS1 4QA"
var company = faker.Company.CompanyName();  // "Thompson & Partners Ltd"

The "en_GB" locale ensures you get UK-formatted data — postcodes rather than zip codes, sensible phone number formats, and British-sounding names.

Generating Complex Objects with Faker

The real power of Bogus is the Faker<T> class, which creates a rule set for generating complete objects:

Example.cs
var userFaker = new Faker<User>("en_GB")
    .RuleFor(u => u.Id, f => f.Random.Guid())
    .RuleFor(u => u.FirstName, f => f.Name.FirstName())
    .RuleFor(u => u.LastName, f => f.Name.LastName())
    .RuleFor(u => u.Email, (f, u) => f.Internet.Email(u.FirstName, u.LastName))
    .RuleFor(u => u.DateOfBirth, f => f.Date.Past(50, DateTime.Now.AddYears(-18)))
    .RuleFor(u => u.IsActive, f => f.Random.Bool(0.9f))
    .RuleFor(u => u.CreatedAt, f => f.Date.Past(2));

var user = userFaker.Generate();
var users = userFaker.Generate(50);

Notice the second rule for Email — it takes both the faker instance and the object being built, so the email can be derived from the already-generated first and last names. This produces coherent data like "[email protected]" rather than mismatched names and addresses.

Deterministic Generation with Seeds

For tests that need repeatable data, set a seed:

Example.cs
Randomizer.Seed = new Random(42);

var userFaker = new Faker<User>("en_GB")
    .RuleFor(u => u.FirstName, f => f.Name.FirstName())
    .RuleFor(u => u.LastName, f => f.Name.LastName());

var user = userFaker.Generate();
// Always produces the same user when seeded with 42

This is critical for snapshot tests and any test where you need to assert against specific values. Without a seed, generated data changes on every run, which makes tests non-deterministic.

Real data has relationships. Use RuleFor with cross-references to build coherent object graphs:

Example.cs
var addressFaker = new Faker<Address>("en_GB")
    .RuleFor(a => a.Line1, f => f.Address.StreetAddress())
    .RuleFor(a => a.City, f => f.Address.City())
    .RuleFor(a => a.PostCode, f => f.Address.ZipCode()) // Uses UK format with en_GB
    .RuleFor(a => a.Country, "United Kingdom");

var orderLineFaker = new Faker<OrderLine>()
    .RuleFor(ol => ol.ProductName, f => f.Commerce.ProductName())
    .RuleFor(ol => ol.Quantity, f => f.Random.Int(1, 10))
    .RuleFor(ol => ol.UnitPrice, f => decimal.Parse(f.Commerce.Price(1, 100)));

var orderFaker = new Faker<Order>("en_GB")
    .RuleFor(o => o.Id, f => f.Random.Guid())
    .RuleFor(o => o.CustomerName, f => f.Name.FullName())
    .RuleFor(o => o.ShippingAddress, f => addressFaker.Generate())
    .RuleFor(o => o.Lines, f => orderLineFaker.Generate(f.Random.Int(1, 5)))
    .RuleFor(o => o.CreatedAt, f => f.Date.Recent(30));

var orders = orderFaker.Generate(10);

Using RuleSet for Variants

Sometimes you need different variations of the same entity:

Example.cs
var userFaker = new Faker<User>("en_GB")
    .RuleFor(u => u.FirstName, f => f.Name.FirstName())
    .RuleFor(u => u.LastName, f => f.Name.LastName())
    .RuleFor(u => u.Email, (f, u) => f.Internet.Email(u.FirstName, u.LastName))
    .RuleFor(u => u.IsActive, true)
    .RuleSet("deactivated", set =>
    {
        set.RuleFor(u => u.IsActive, false);
        set.RuleFor(u => u.DeactivatedAt, f => f.Date.Past(1));
    });

var activeUser = userFaker.Generate();
var deactivatedUser = userFaker.Generate("default,deactivated");

Combining with Test Data Builders

Bogus and the builder pattern work well together. Use Bogus for realistic defaults and the builder for test-specific overrides:

Example.cs
public class UserBuilder
{
    private static readonly Faker _faker = new("en_GB");

    private string _firstName = _faker.Name.FirstName();
    private string _lastName = _faker.Name.LastName();
    private string _email;
    private bool _isActive = true;

    public UserBuilder()
    {
        _email = _faker.Internet.Email(_firstName, _lastName);
    }

    public UserBuilder WithName(string first, string last)
    {
        _firstName = first;
        _lastName = last;
        _email = _faker.Internet.Email(first, last);
        return this;
    }

    public UserBuilder Inactive()
    {
        _isActive = false;
        return this;
    }

    public User Build() => new()
    {
        FirstName = _firstName,
        LastName = _lastName,
        Email = _email,
        IsActive = _isActive
    };
}

Available Data Categories

Bogus provides generators for a wide range of data types:

Each category respects the locale you've configured, so you get data that looks right for your target audience. For UK-based applications, en_GB is the locale you want.