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
dotnet add package Bogus
Basic Usage with Faker
The Faker class provides access to data generators organised by category:
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:
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:
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.
Generating Related Entities
Real data has relationships. Use RuleFor with cross-references to build coherent object graphs:
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:
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:
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:
- Person — names, gender, date of birth
- Internet — email, username, URL, IP address, user agent
- Address — street, city, postcode, country, coordinates
- Commerce — product name, price, category, department
- Finance — account number, IBAN, BIC, currency, amount
- Lorem — words, sentences, paragraphs
- Date — past, future, recent, between, timespan
- Phone — phone number in locale-appropriate format
- System — file name, MIME type, file extension
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.