Bogus: Generate Realistic Fake Data in .NET Tests
Bogus is a .NET fake data generator that creates realistic-looking test data — names, emails, addresses, phone numbers, Lorem Ipsum text — with a fluent API, making it easy to build repeatable, locale-aware test fixtures.
Key Takeaways
- Faker
with RuleFor() creates a typed data factory that generates consistent, realistic objects - Seed the randomizer with a fixed integer for reproducible test runs — same seed always produces the same data
- Bogus ships over 60 built-in datasets covering names, internet, addresses, commerce, finance, lorem, and more
- GenerateBetween(min, max) lets you vary the number of child records to test boundary conditions
- Bogus produces more natural-looking data than AutoFixture and gives you explicit control over every field
Hand-crafting test data is tedious. new User { Name = "Test User", Email = "test@test.com" } fills the database with rows that look nothing like production data, which means tests pass against artificial inputs and fail or behave unexpectedly once real-world data arrives. Bogus solves this by generating realistic fake data — names that look like names, email addresses formatted correctly, phone numbers with valid area codes, lorem ipsum paragraphs of the right length.
This post covers the full Bogus API: building typed fakers with Faker<T>, using built-in datasets, seeding for reproducibility, locale support, and composing entity factories for complex domain models.
Installing Bogus
dotnet add package BogusBogus targets .NET Standard 1.3+ and works on .NET Framework and all modern .NET versions. No additional configuration is required.
The Faker Class
Faker<T> is the core of Bogus. You subclass or instantiate it and use RuleFor() to specify how each property should be populated.
using Bogus;
public class User
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public DateTime DateOfBirth { get; set; }
public bool IsActive { get; set; }
}
// Define the faker
var userFaker = new Faker<User>()
.RuleFor(u => u.Id, f => f.IndexFaker + 1)
.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.Person.DateOfBirth)
.RuleFor(u => u.IsActive, f => f.Random.Bool(0.9f)); // 90% active
// Generate a single user
User user = userFaker.Generate();
// Generate a list of 10 users
List<User> users = userFaker.Generate(10);
// Generate between 5 and 15 users
List<User> variableUsers = userFaker.GenerateBetween(5, 15);The f parameter in each RuleFor() lambda is a Faker instance that provides access to all the built-in datasets. The two-argument form (f, u) => ... gives you access to both the faker and the partially-built object, useful for deriving one field from another (like building an email from the first and last name already set).
f.IndexFaker increments for each generated object, giving you unique sequential IDs within a generation run.
Built-In Datasets
Bogus ships over 60 datasets covering virtually every type of realistic data you might need.
var f = new Faker();
// Names
f.Name.FirstName(); // "Alice"
f.Name.LastName(); // "Hernandez"
f.Name.FullName(); // "Alice Hernandez"
f.Name.JobTitle(); // "Senior Software Engineer"
f.Name.Prefix(); // "Dr."
// Internet
f.Internet.Email(); // "alice.hernandez@example.net"
f.Internet.UserName(); // "alice.hernandez42"
f.Internet.Url(); // "https://example.org/path"
f.Internet.Ip(); // "192.168.1.42"
f.Internet.Password(); // "xK!4mPq9"
f.Internet.Avatar(); // Gravatar URL
// Address
f.Address.City(); // "San Francisco"
f.Address.State(); // "California"
f.Address.StateAbbr(); // "CA"
f.Address.ZipCode(); // "94102"
f.Address.Country(); // "United States"
f.Address.StreetAddress(); // "123 Main St"
f.Address.Latitude(); // 37.7749
f.Address.Longitude(); // -122.4194
// Phone
f.Phone.PhoneNumber(); // "+1-415-555-0123"
f.Phone.PhoneNumberFormat(1); // formatted per locale
// Commerce
f.Commerce.ProductName(); // "Ergonomic Steel Keyboard"
f.Commerce.Department(); // "Electronics"
f.Commerce.Price(); // "29.99"
f.Commerce.Color(); // "sky blue"
f.Commerce.Ean13(); // "5901234123457"
// Lorem ipsum
f.Lorem.Word(); // "sapiente"
f.Lorem.Words(5); // ["sapiente", "ut", ...]
f.Lorem.Sentence(); // "Ut enim ad minim veniam."
f.Lorem.Paragraph(); // multi-sentence paragraph
f.Lorem.Paragraphs(3); // 3 paragraphs
f.Lorem.Text(); // any length text
// Dates
f.Date.Past(2); // random date within last 2 years
f.Date.Future(1); // random date within next year
f.Date.Between(start, end); // between two dates
f.Date.Recent(7); // within last 7 days
// Numbers and random
f.Random.Int(1, 100); // random int in range
f.Random.Decimal(0, 999.99m);
f.Random.Bool();
f.Random.Enum<OrderStatus>(); // random enum value
f.Random.ArrayElement(new[] { "a", "b", "c" });
f.Random.ListItem(myList);
f.Random.Guid();Seeding for Reproducible Tests
Without seeding, every call to Generate() produces different data. This is great for finding edge cases but bad for deterministic tests. A fixed seed ensures the same data every run.
// Set global seed — affects all Faker instances
Randomizer.Seed = new Random(12345);
var userFaker = new Faker<User>()
.RuleFor(u => u.FirstName, f => f.Name.FirstName())
.RuleFor(u => u.LastName, f => f.Name.LastName());
var user1 = userFaker.Generate(); // always the same "Alice Hernandez"
var user2 = userFaker.Generate(); // always the same "Bob Chen"
// Per-faker seed (doesn't affect global state)
var seededFaker = new Faker<User>()
.UseSeed(99999)
.RuleFor(u => u.FirstName, f => f.Name.FirstName());
// Good pattern: seed in test setup, reset in teardown
public class UserTests : IDisposable
{
public UserTests()
{
Randomizer.Seed = new Random(42); // fixed seed for this test class
}
public void Dispose()
{
Randomizer.Seed = new Random(); // reset to time-based seed
}
}Using a fixed seed is especially important for snapshot tests or any test where you're storing expected output. Commit the seed value alongside the expected data so the test is always deterministic.
Locale Support
Bogus supports over 40 locales. Switching locale affects names, addresses, phone formats, and other region-specific data.
// English (default)
var enFaker = new Faker("en");
// German
var deFaker = new Faker("de");
deFaker.Name.FirstName(); // "Klaus"
deFaker.Address.City(); // "München"
deFaker.Phone.PhoneNumber(); // "+49 89 123456"
// Japanese
var jaFaker = new Faker("ja");
jaFaker.Name.FullName(); // "田中 美咲"
// Brazilian Portuguese
var ptBrFaker = new Faker("pt_BR");
// Apply locale to a typed Faker
var germanUserFaker = new Faker<User>("de")
.RuleFor(u => u.FirstName, f => f.Name.FirstName())
.RuleFor(u => u.LastName, f => f.Name.LastName());Locale support is useful when testing internationalization features — validating that your UI handles non-ASCII names correctly, or that address formatting works across regions.
Building Entity Factories for Complex Domain Models
Real applications have nested object graphs. Bogus handles these cleanly by composing fakers.
public class Address
{
public string Street { get; set; }
public string City { get; set; }
public string PostalCode { get; set; }
public string Country { get; set; }
}
public class OrderLineItem
{
public int ProductId { get; set; }
public string ProductName { get; set; }
public int Quantity { get; set; }
public decimal UnitPrice { get; set; }
}
public class Order
{
public Guid Id { get; set; }
public User Customer { get; set; }
public Address ShippingAddress { get; set; }
public List<OrderLineItem> Items { get; set; }
public DateTime PlacedAt { get; set; }
public OrderStatus Status { get; set; }
}
// Build fakers from the bottom up
var addressFaker = new Faker<Address>()
.RuleFor(a => a.Street, f => f.Address.StreetAddress())
.RuleFor(a => a.City, f => f.Address.City())
.RuleFor(a => a.PostalCode, f => f.Address.ZipCode())
.RuleFor(a => a.Country, f => f.Address.Country());
var lineItemFaker = new Faker<OrderLineItem>()
.RuleFor(li => li.ProductId, f => f.Random.Int(1, 1000))
.RuleFor(li => li.ProductName, f => f.Commerce.ProductName())
.RuleFor(li => li.Quantity, f => f.Random.Int(1, 10))
.RuleFor(li => li.UnitPrice, f => f.Random.Decimal(0.99m, 499.99m));
var orderFaker = new Faker<Order>()
.RuleFor(o => o.Id, f => f.Random.Guid())
.RuleFor(o => o.Customer, f => userFaker.Generate())
.RuleFor(o => o.ShippingAddress, f => addressFaker.Generate())
.RuleFor(o => o.Items, f => lineItemFaker.GenerateBetween(1, 8))
.RuleFor(o => o.PlacedAt, f => f.Date.Recent(30))
.RuleFor(o => o.Status, f => f.Random.Enum<OrderStatus>());
// Generate test data
var order = orderFaker.Generate();
var orders = orderFaker.Generate(50); // 50 orders for load testing dataCustomizing Generated Objects with FinishWith
Sometimes you need post-generation logic that depends on multiple fields already being set.
var orderFaker = new Faker<Order>()
.RuleFor(o => o.Items, f => lineItemFaker.GenerateBetween(1, 5))
.RuleFor(o => o.Status, f => f.Random.Enum<OrderStatus>())
.FinishWith((f, o) => {
// Compute total from items after they're generated
o.Total = o.Items.Sum(i => i.Quantity * i.UnitPrice);
// Ensure cancelled orders have no shipping date
if (o.Status == OrderStatus.Cancelled)
o.ShippedAt = null;
});Using StrictMode for Safety
By default, Faker<T> silently leaves unconfigured properties at their default values. In StrictMode, any property without a RuleFor() causes an exception when you call Generate().
var strictFaker = new Faker<User>()
.StrictMode(true)
.RuleFor(u => u.Id, f => f.IndexFaker + 1)
.RuleFor(u => u.FirstName, f => f.Name.FirstName());
// Missing RuleFor for LastName, Email, etc.
var user = strictFaker.Generate(); // throws — uncovered propertiesThis is useful during initial setup to make sure you haven't accidentally left a required field unset.
Bogus vs AutoFixture
Both libraries generate test data, but with different philosophies.
AutoFixture uses reflection to populate all properties automatically with random values. This requires less setup code but produces less realistic data (auto-generated strings look like GUIDs, not names).
Bogus requires explicit RuleFor() declarations but produces realistic, domain-appropriate data. The tradeoff is more upfront work in exchange for data that surfaces real-world edge cases and makes test output readable.
// AutoFixture — automatic but unrealistic
var fixture = new Fixture();
var user = fixture.Create<User>();
// user.FirstName = "4f9e3c2a-1b5d..." (GUID-looking string)
// Bogus — explicit but realistic
var user = userFaker.Generate();
// user.FirstName = "Maria" (looks like a real name)For unit tests where you only care about specific fields and use the rest as filler, AutoFixture is faster. For integration tests, seed data generation, or any test where data realism matters, Bogus wins.
A Complete Test Example
[Fact]
public async Task BulkImport_should_process_valid_users_and_skip_invalid()
{
Randomizer.Seed = new Random(2024);
var validUsers = new Faker<User>()
.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)
.Generate(20);
var invalidUsers = new Faker<User>()
.RuleFor(u => u.FirstName, f => f.Name.FirstName())
.RuleFor(u => u.Email, _ => "not-an-email") // deliberately invalid
.Generate(5);
var allUsers = validUsers.Concat(invalidUsers).ToList();
var result = await importService.BulkImportAsync(allUsers);
result.Imported.Should().Be(20);
result.Failed.Should().Be(5);
result.Errors.Should().AllSatisfy(e =>
e.Reason.Should().Contain("email"));
}Conclusion
Bogus turns test data from a chore into a tool. Once you have a Faker<T> for your domain entities, generating one object or a thousand takes one line. The data looks realistic, which means your tests catch real-world formatting issues, your seed scripts produce convincing demo data, and your integration test logs are readable. Combined with seeding for determinism and locale support for i18n testing, Bogus covers the full spectrum of fake data needs in a .NET project.