Generating Test Data in .NET with Bogus and AutoFixture
Hand-crafting test data is tedious and leads to brittle tests. When you hard-code new User { Name = "Alice", Email = "alice@test.com" } in 40 tests, changing the User constructor breaks all 40. When tests share the same magic string "alice@test.com", a bug that only manifests with certain email formats goes undetected.
Two libraries solve this: Bogus for generating realistic fake data, and AutoFixture for automating object creation. They solve different problems and work well together.
Bogus: Realistic Fake Data
Bogus is a .NET port of Faker.js. It generates contextually realistic data — actual-looking names, addresses, company names, phone numbers, and more.
dotnet add package BogusBasic Usage
using Bogus;
var faker = new Faker();
string name = faker.Name.FullName(); // "Jane Smith"
string email = faker.Internet.Email(); // "jane.smith@example.net"
string phone = faker.Phone.PhoneNumber(); // "+1 555-234-5678"
string address = faker.Address.StreetAddress(); // "123 Oak Street"
string company = faker.Company.CompanyName(); // "Acme Corp LLC"
string lorem = faker.Lorem.Sentence(); // "Lorem ipsum dolor sit amet."
int age = faker.Random.Int(18, 80); // 42Generating Typed Objects with Faker
The real power is Faker<T>, which generates complete objects:
var userFaker = new Faker<User>()
.RuleFor(u => u.Id, f => f.IndexFaker + 1)
.RuleFor(u => u.Name, f => f.Name.FullName())
.RuleFor(u => u.Email, f => f.Internet.Email())
.RuleFor(u => u.Age, f => f.Random.Int(18, 65))
.RuleFor(u => u.CreatedAt, f => f.Date.Past(2));
User user = userFaker.Generate();
List<User> users = userFaker.Generate(10);Each call to Generate() produces a different object with realistic-looking data.
Seeding for Reproducibility
Random data is great until a test fails and you can't reproduce it:
// Same seed = same data every time
var faker = new Faker<User>()
.RuleFor(u => u.Name, f => f.Name.FullName())
.RuleFor(u => u.Email, f => f.Internet.Email());
faker.UseSeed(1234);
User user1 = faker.Generate(); // always the same
// Or seed globally
Randomizer.Seed = new Random(42);Use a fixed seed in tests where you need reproducibility. Use random seeds in property-based-style tests where you want to catch edge cases.
Locale Support
Bogus ships with locale-specific data:
var frenchFaker = new Faker("fr");
string name = frenchFaker.Name.FullName(); // "Jean-Pierre Dupont"
string city = frenchFaker.Address.City(); // "Lyon"
var japaneseFaker = new Faker("ja");
string jpName = japaneseFaker.Name.FullName();Useful when your application handles international data and you want tests to reflect that.
Practical Test Example with Bogus
public class UserServiceTests
{
private readonly Faker<User> _userFaker = new Faker<User>()
.RuleFor(u => u.Id, f => f.Random.Guid())
.RuleFor(u => u.Name, f => f.Name.FullName())
.RuleFor(u => u.Email, f => f.Internet.Email())
.RuleFor(u => u.IsActive, true);
[Test]
public void GetActiveUsers_ReturnsOnlyActiveUsers()
{
var activeUsers = _userFaker.Generate(5);
var inactiveUsers = _userFaker
.RuleFor(u => u.IsActive, false)
.Generate(3);
var all = activeUsers.Concat(inactiveUsers).ToList();
var repository = new InMemoryUserRepository(all);
var service = new UserService(repository);
var result = service.GetActiveUsers();
result.Should().HaveCount(5);
result.Should().OnlyContain(u => u.IsActive);
}
}The test doesn't care what the users' names or emails are — it only cares about IsActive. Bogus generates the rest without you having to invent it.
AutoFixture: Automated Object Creation
AutoFixture takes a different approach. Instead of defining what each property contains, it automatically creates objects with populated properties — you just tell it what type you want.
dotnet add package AutoFixtureBasic Usage
using AutoFixture;
var fixture = new Fixture();
string randomString = fixture.Create<string>(); // "a3f7b2c1..."
int randomInt = fixture.Create<int>(); // 47
User user = fixture.Create<User>(); // fully populated UserAutoFixture creates User by calling its constructor with generated arguments and setting all public properties. You don't configure anything.
Why This Matters
Suppose your Order class has 15 properties, but your test only cares about Total. Without AutoFixture:
// Must provide all required constructor args
var order = new Order
{
Id = 1,
CustomerId = Guid.NewGuid(),
CustomerName = "Test",
Items = new List<OrderItem>(),
CreatedAt = DateTime.UtcNow,
Status = OrderStatus.Pending,
// ... 9 more properties
Total = 99.99m
};With AutoFixture:
var order = fixture.Build<Order>()
.With(o => o.Total, 99.99m)
.Create();AutoFixture handles the other 14 properties. Your test focuses on what it actually tests.
Customizing AutoFixture
var fixture = new Fixture();
// Fix a specific property
var order = fixture.Build<Order>()
.With(o => o.Status, OrderStatus.Confirmed)
.Without(o => o.CancelledAt) // set to default
.Create();
// Register a factory for a type
fixture.Register<decimal>(() => Math.Round(new Random().NextDouble() * 100, 2));
// Freeze a value (all requests for this type return the same value)
var userId = fixture.Freeze<Guid>();
var user = fixture.Create<User>(); // user.Id == userId
var order = fixture.Create<Order>(); // order.UserId == userIdFreeze is particularly useful when you need related objects to share the same identifier.
AutoFixture with Data Annotations
AutoFixture respects data annotations:
public class UserDto
{
[MaxLength(100)]
public string Name { get; set; }
[EmailAddress]
public string Email { get; set; }
[Range(18, 120)]
public int Age { get; set; }
}Install the annotations extension:
dotnet add package AutoFixture.DataAnnotationsfixture.Customize(new AutoDataAnnotationsCustomization());
var dto = fixture.Create<UserDto>(); // respects MaxLength, Range, etc.NUnit and xUnit Integration
AutoFixture has test framework integrations that eliminate fixture.Create<T>() boilerplate:
xUnit:
dotnet add package AutoFixture.Xunit2using AutoFixture.Xunit2;
public class OrderTests
{
[Theory, AutoData]
public void CalculateTotal_ReturnsCorrectSum(
List<OrderItem> items, // auto-generated
Order order) // auto-generated
{
order.Items = items;
var total = order.CalculateTotal();
total.Should().Be(items.Sum(i => i.Price * i.Quantity));
}
}AutoFixture generates the method parameters automatically. You focus on the logic.
NUnit:
dotnet add package AutoFixture.NUnit3[Test, AutoData]
public void ProcessPayment_ValidCard_ReturnsSuccess(
PaymentRequest request,
[Frozen] Mock<IPaymentGateway> gateway)
{
gateway.Setup(g => g.Charge(request)).Returns(PaymentResult.Success);
var service = new PaymentService(gateway.Object);
var result = service.Process(request);
result.Should().Be(PaymentResult.Success);
}Combining Bogus and AutoFixture
They solve different problems and complement each other:
- Bogus: when you need realistic-looking data (names, emails, addresses) that makes failures meaningful
- AutoFixture: when you need an object populated and don't care what the values are
You can use Bogus within AutoFixture:
var faker = new Faker();
var fixture = new Fixture();
fixture.Customize<User>(c => c
.With(u => u.Name, () => faker.Name.FullName())
.With(u => u.Email, () => faker.Internet.Email())
.OmitAutoProperties());
var user = fixture.Create<User>(); // "Jane Smith", "jane@example.net"Or keep them separate:
- Use
Faker<T>for entities that end up in failure messages (users, orders) where realistic names help you understand what failed - Use
fixture.Create<T>()for request/response DTOs where you just need valid data
Common Pitfalls
Don't use magic values in faker rules:
// Bad: specific email defeats the purpose
.RuleFor(u => u.Email, "test@test.com")
// Good: realistic variation
.RuleFor(u => u.Email, f => f.Internet.Email())Seed when debugging production failures: When a test fails with random data, log the seed and use it to reproduce:
var seed = Environment.TickCount;
Console.WriteLine($"Seed: {seed}"); // capture from test output
Randomizer.Seed = new Random(seed);AutoFixture doesn't know your domain rules:
// AutoFixture might set StartDate after EndDate
// Fix it explicitly:
var campaign = fixture.Build<Campaign>()
.With(c => c.StartDate, DateTime.Today)
.With(c => c.EndDate, DateTime.Today.AddDays(30))
.Create();Automated test data generation reduces setup noise, catches edge cases you wouldn't think to write, and makes tests resilient to model changes. It's one of the highest-leverage habits you can build as a .NET developer.
Automated test data gets you further in unit tests. For end-to-end and monitoring coverage of your .NET applications, HelpMeTest provides plain-English test creation and 24/7 monitoring without code.