Test Data Generation
Generate realistic test data using Bogus, test builders, and ABP data seeders.
When to Use
-
Creating realistic fake data for tests
-
Building complex object graphs for testing
-
Seeding test databases
-
Generating development data
-
Creating deterministic test fixtures
Bogus Faker Setup
Installation
<PackageReference Include="Bogus" Version="35.*" />
Basic Faker Configuration
using Bogus;
public static class FakeDataGenerators { // Set seed for reproducible tests public static int Seed { get; set; } = 12345;
static FakeDataGenerators()
{
Randomizer.Seed = new Random(Seed);
}
public static Faker<Patient> PatientFaker => new Faker<Patient>()
.RuleFor(p => p.Id, f => Guid.NewGuid())
.RuleFor(p => p.FirstName, f => f.Name.FirstName())
.RuleFor(p => p.LastName, f => f.Name.LastName())
.RuleFor(p => p.Email, (f, p) => f.Internet.Email(p.FirstName, p.LastName))
.RuleFor(p => p.Phone, f => f.Phone.PhoneNumber("###-###-####"))
.RuleFor(p => p.DateOfBirth, f => f.Date.Past(80, DateTime.Today.AddYears(-18)))
.RuleFor(p => p.Address, f => f.Address.FullAddress())
.RuleFor(p => p.Status, f => f.PickRandom<PatientStatus>());
public static Faker<Doctor> DoctorFaker => new Faker<Doctor>()
.RuleFor(d => d.Id, f => Guid.NewGuid())
.RuleFor(d => d.Name, f => $"Dr. {f.Name.FullName()}")
.RuleFor(d => d.Specialization, f => f.PickRandom(
"Cardiology", "Neurology", "Pediatrics", "Orthopedics"))
.RuleFor(d => d.LicenseNumber, f => f.Random.AlphaNumeric(10).ToUpper())
.RuleFor(d => d.YearsOfExperience, f => f.Random.Int(1, 40));
public static Faker<Appointment> AppointmentFaker => new Faker<Appointment>()
.RuleFor(a => a.Id, f => Guid.NewGuid())
.RuleFor(a => a.DateTime, f => f.Date.Future(30))
.RuleFor(a => a.Duration, f => TimeSpan.FromMinutes(f.PickRandom(15, 30, 45, 60)))
.RuleFor(a => a.Notes, f => f.Lorem.Sentence())
.RuleFor(a => a.Status, f => f.PickRandom<AppointmentStatus>());
}
Advanced Bogus Patterns
public static class AdvancedFakers { // Faker with relationships public static Faker<Appointment> AppointmentWithRelationsFaker( Guid? patientId = null, Guid? doctorId = null) { return new Faker<Appointment>() .RuleFor(a => a.Id, f => Guid.NewGuid()) .RuleFor(a => a.PatientId, f => patientId ?? Guid.NewGuid()) .RuleFor(a => a.DoctorId, f => doctorId ?? Guid.NewGuid()) .RuleFor(a => a.DateTime, f => f.Date.Future(30)) .RuleFor(a => a.Status, AppointmentStatus.Scheduled); }
// Faker with conditional rules
public static Faker<Patient> PatientFakerWithScenario(PatientScenario scenario)
{
var faker = new Faker<Patient>()
.RuleFor(p => p.Id, f => Guid.NewGuid())
.RuleFor(p => p.Name, f => f.Name.FullName())
.RuleFor(p => p.Email, f => f.Internet.Email());
return scenario switch
{
PatientScenario.Adult => faker
.RuleFor(p => p.DateOfBirth, f => f.Date.Past(50, DateTime.Today.AddYears(-18))),
PatientScenario.Minor => faker
.RuleFor(p => p.DateOfBirth, f => f.Date.Past(17, DateTime.Today.AddYears(-1))),
PatientScenario.Elderly => faker
.RuleFor(p => p.DateOfBirth, f => f.Date.Past(30, DateTime.Today.AddYears(-65))),
_ => faker
};
}
// Faker with locale
public static Faker<Patient> BrazilianPatientFaker => new Faker<Patient>("pt_BR")
.RuleFor(p => p.Name, f => f.Name.FullName())
.RuleFor(p => p.Phone, f => f.Phone.PhoneNumber());
// Generate unique values
public static Faker<Patient> UniquePatientFaker => new Faker<Patient>()
.RuleFor(p => p.Id, f => Guid.NewGuid())
.RuleFor(p => p.Email, f => f.Internet.Email().ToLower())
.RuleFor(p => p.SocialSecurityNumber, f => f.Random.Replace("###-##-####"));
}
public enum PatientScenario { Adult, Minor, Elderly }
Test Builder Pattern
Fluent Builder
public class PatientBuilder { private readonly Patient _patient; private readonly Faker _faker = new();
public PatientBuilder()
{
_patient = new Patient
{
Id = Guid.NewGuid(),
Name = _faker.Name.FullName(),
Email = _faker.Internet.Email(),
DateOfBirth = _faker.Date.Past(50, DateTime.Today.AddYears(-18)),
Status = PatientStatus.Active
};
}
public PatientBuilder WithId(Guid id)
{
_patient.Id = id;
return this;
}
public PatientBuilder WithName(string name)
{
_patient.Name = name;
return this;
}
public PatientBuilder WithEmail(string email)
{
_patient.Email = email;
return this;
}
public PatientBuilder WithDateOfBirth(DateTime dob)
{
_patient.DateOfBirth = dob;
return this;
}
public PatientBuilder AsMinor()
{
_patient.DateOfBirth = DateTime.Today.AddYears(-10);
return this;
}
public PatientBuilder AsInactive()
{
_patient.Status = PatientStatus.Inactive;
return this;
}
public PatientBuilder WithAppointments(int count)
{
_patient.Appointments = FakeDataGenerators.AppointmentFaker
.Generate(count)
.Select(a => { a.PatientId = _patient.Id; return a; })
.ToList();
return this;
}
public Patient Build() => _patient;
// Implicit conversion for cleaner tests
public static implicit operator Patient(PatientBuilder builder) => builder.Build();
}
// Usage var patient = new PatientBuilder() .WithName("John Doe") .WithEmail("john@example.com") .AsMinor() .WithAppointments(3) .Build();
DTO Builder
public class CreatePatientDtoBuilder { private readonly CreatePatientDto _dto; private readonly Faker _faker = new();
public CreatePatientDtoBuilder()
{
_dto = new CreatePatientDto
{
Name = _faker.Name.FullName(),
Email = _faker.Internet.Email(),
Phone = _faker.Phone.PhoneNumber(),
DateOfBirth = _faker.Date.Past(50, DateTime.Today.AddYears(-18))
};
}
public CreatePatientDtoBuilder WithName(string name)
{
_dto.Name = name;
return this;
}
public CreatePatientDtoBuilder WithEmail(string email)
{
_dto.Email = email;
return this;
}
public CreatePatientDtoBuilder WithInvalidEmail()
{
_dto.Email = "not-an-email";
return this;
}
public CreatePatientDtoBuilder WithEmptyName()
{
_dto.Name = "";
return this;
}
public CreatePatientDto Build() => _dto;
}
// Usage in tests [Fact] public async Task Create_InvalidEmail_ReturnsValidationError() { var input = new CreatePatientDtoBuilder() .WithInvalidEmail() .Build();
var result = await _service.CreateAsync(input);
// Assert...
}
ABP Data Seeders
Test Data Seeder
public class TestDataSeeder : IDataSeedContributor, ITransientDependency { private readonly IRepository<Patient, Guid> _patientRepository; private readonly IRepository<Doctor, Guid> _doctorRepository; private readonly IGuidGenerator _guidGenerator;
public TestDataSeeder(
IRepository<Patient, Guid> patientRepository,
IRepository<Doctor, Guid> doctorRepository,
IGuidGenerator guidGenerator)
{
_patientRepository = patientRepository;
_doctorRepository = doctorRepository;
_guidGenerator = guidGenerator;
}
public async Task SeedAsync(DataSeedContext context)
{
if (await _patientRepository.GetCountAsync() > 0)
return;
// Seed deterministic test data
var patients = CreateTestPatients();
await _patientRepository.InsertManyAsync(patients);
var doctors = CreateTestDoctors();
await _doctorRepository.InsertManyAsync(doctors);
}
private List<Patient> CreateTestPatients()
{
return new List<Patient>
{
new Patient(TestDataIds.Patient1, "John Doe", "john@test.com",
new DateTime(1990, 5, 15)),
new Patient(TestDataIds.Patient2, "Jane Smith", "jane@test.com",
new DateTime(1985, 8, 22)),
new Patient(TestDataIds.InactivePatient, "Inactive User", "inactive@test.com",
new DateTime(1970, 1, 1)) { Status = PatientStatus.Inactive },
new Patient(TestDataIds.DeletablePatient, "To Be Deleted", "delete@test.com",
new DateTime(1995, 3, 10))
};
}
private List<Doctor> CreateTestDoctors()
{
return new List<Doctor>
{
new Doctor(TestDataIds.Doctor1, "Dr. House", "Diagnostics", "LIC001"),
new Doctor(TestDataIds.Doctor2, "Dr. Wilson", "Oncology", "LIC002")
};
}
}
// Centralized test IDs public static class TestDataIds { public static readonly Guid Patient1 = Guid.Parse("3a1b2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d"); public static readonly Guid Patient2 = Guid.Parse("4b2c3d4e-5f6a-7b8c-9d0e-1f2a3b4c5d6e"); public static readonly Guid InactivePatient = Guid.Parse("5c3d4e5f-6a7b-8c9d-0e1f-2a3b4c5d6e7f"); public static readonly Guid DeletablePatient = Guid.Parse("6d4e5f6a-7b8c-9d0e-1f2a-3b4c5d6e7f8a"); public static readonly Guid Doctor1 = Guid.Parse("7e5f6a7b-8c9d-0e1f-2a3b-4c5d6e7f8a9b"); public static readonly Guid Doctor2 = Guid.Parse("8f6a7b8c-9d0e-1f2a-3b4c-5d6e7f8a9b0c"); }
Bulk Data Seeder for Performance Tests
public class BulkTestDataSeeder : IDataSeedContributor, ITransientDependency { private readonly IRepository<Patient, Guid> _patientRepository;
public async Task SeedAsync(DataSeedContext context)
{
if (!context.Properties.ContainsKey("BulkSeed"))
return;
var count = (int)context.Properties["BulkSeed"];
var patients = FakeDataGenerators.PatientFaker.Generate(count);
// Batch insert for performance
const int batchSize = 1000;
for (int i = 0; i < patients.Count; i += batchSize)
{
var batch = patients.Skip(i).Take(batchSize).ToList();
await _patientRepository.InsertManyAsync(batch);
}
}
}
// Usage in test [Fact] public async Task GetList_LargeDataset_PerformsWell() { await SeedDataAsync(new DataSeedContext() .WithProperty("BulkSeed", 10000));
var stopwatch = Stopwatch.StartNew();
var result = await _service.GetListAsync(new GetPatientListDto());
stopwatch.Stop();
stopwatch.ElapsedMilliseconds.ShouldBeLessThan(500);
}
Test Fixtures
Shared Fixture for Integration Tests
public class DatabaseFixture : IAsyncLifetime { public IServiceProvider Services { get; private set; } public ClinicDbContext DbContext { get; private set; }
public async Task InitializeAsync()
{
var services = new ServiceCollection();
services.AddDbContext<ClinicDbContext>(options =>
options.UseInMemoryDatabase($"TestDb_{Guid.NewGuid()}"));
Services = services.BuildServiceProvider();
DbContext = Services.GetRequiredService<ClinicDbContext>();
await SeedTestDataAsync();
}
public async Task DisposeAsync()
{
await DbContext.DisposeAsync();
}
private async Task SeedTestDataAsync()
{
var patients = FakeDataGenerators.PatientFaker.Generate(10);
DbContext.Patients.AddRange(patients);
await DbContext.SaveChangesAsync();
}
}
// Usage public class PatientTests : IClassFixture<DatabaseFixture> { private readonly DatabaseFixture _fixture;
public PatientTests(DatabaseFixture fixture)
{
_fixture = fixture;
}
[Fact]
public async Task Test_WithSharedData()
{
var patients = await _fixture.DbContext.Patients.ToListAsync();
patients.ShouldNotBeEmpty();
}
}
Quick Reference
Pattern Use Case Example
Bogus Faker Random realistic data PatientFaker.Generate(10)
Builder Fluent test object creation new PatientBuilder().WithName("John").Build()
Data Seeder Database test data IDataSeedContributor
Fixture Shared test context IClassFixture<T>
Bogus Cheat Sheet
Method Purpose
f.Name.FullName()
Random full name
f.Internet.Email()
Random email
f.Phone.PhoneNumber("###-###-####")
Formatted phone
f.Date.Past(years)
Past date
f.Date.Future(days)
Future date
f.Lorem.Sentence()
Random sentence
f.Random.Int(min, max)
Random integer
f.PickRandom<Enum>()
Random enum value
f.Random.AlphaNumeric(length)
Random string
Related Skills
-
xunit-testing-patterns
-
Test structure and assertions
-
api-integration-testing
-
API test patterns
-
efcore-patterns
-
Database testing