var user = new User { Name = "Test", Email = "test@test.com" } — це не реалістично. Реальні помилки ховаються в крайніх випадках: довгі імена з пробілами та апострофами, email з субдоменами, телефони у різних форматах. Bogus генерує реалістичні дані, що виявляють справжні проблеми.Ручне написання тестових даних має конкретні недоліки:
Проблема 1: Нереалістичність. "test@test.com" ніколи не виявить проблему з валідацією довгих субдоменів. "John Doe" не виявить проблему з апострофом у "O'Brien".
Проблема 2: Одноманітність. Коли всі тести використовують ті самі дані, деякі класи помилок залишаються невидимими.
Проблема 3: Трудоємність. Написання 50 унікальних записів для seed-data вручну — марна трата часу.
Bogus вирішує це через генерацію реалістичних даних, що відповідають реальним закономірностям: імена у правильному регістрі, валідні формати email, телефони у стандартних форматах, адреси з реальними назвами вулиць.
dotnet add package Bogus
Центральний клас — Faker<T>. Він описує, як генерувати екземпляри типу T:
using Bogus;
// Локаль "uk" — українські дані
var faker = new Faker<User>("uk")
// RuleFor: задаємо правило для кожного поля
.RuleFor(u => u.Id, f => f.IndexFaker + 1) // Послідовний ID
.RuleFor(u => u.FirstName, f => f.Name.FirstName())
.RuleFor(u => u.LastName, f => f.Name.LastName())
.RuleFor(u => u.Email, (f, u) => // Другий аргумент — сам об'єкт
// Email на основі вже згенерованого імені
f.Internet.Email(u.FirstName, u.LastName))
.RuleFor(u => u.Phone, f => f.Phone.PhoneNumber("+380-##-###-##-##"))
.RuleFor(u => u.BirthDate, f => f.Date.Past(45, refDate: DateTime.Now.AddYears(-18)))
.RuleFor(u => u.Role, f => f.PickRandom<UserRole>())
.RuleFor(u => u.IsActive, f => f.Random.Bool(weight: 0.85f)) // 85% активних
.RuleFor(u => u.CreatedAt, f => f.Date.Past(2)); // Протягом останніх 2 років
// Генеруємо один об'єкт
var user = faker.Generate();
// Генеруємо 100 об'єктів
var users = faker.Generate(100);
Console.WriteLine($"{user.FirstName} {user.LastName} <{user.Email}>");
// → Олена Петренко <olena.petrenko@gmail.com>
Bogus організує генератори у тематичні категорії:
var f = new Faker("uk");
// Імена
f.Name.FirstName() // "Олена"
f.Name.LastName() // "Ковальчук"
f.Name.FullName() // "Ігор Петренко"
f.Name.JobTitle() // "Senior Product Manager"
f.Name.JobArea() // "Operations"
// Internet
f.Internet.Email() // "test.user@example.ua"
f.Internet.Email("Олена", "Ковальчук") // "olena.kovalchuk83@gmail.com"
f.Internet.Url() // "https://example.net/path"
f.Internet.UserName() // "olena_k42"
f.Internet.Password(length: 12) // "aKj#9Lm!2xZ7"
f.Internet.Ip() // "192.168.1.42"
f.Internet.UserAgent() // "Mozilla/5.0 ..."
// Address
f.Address.Country() // "Польща"
f.Address.City() // "Харків"
f.Address.StreetAddress() // "вул. Лесі Українки, 25"
f.Address.ZipCode() // "61000"
f.Address.Latitude() // 50.4501
f.Address.Longitude() // 30.5234
// Commerce (e-commerce дані)
f.Commerce.ProductName() // "Handcrafted Steel Keyboard"
f.Commerce.Product() // "Bacon" (категорія продукту)
f.Commerce.Department() // "Computers"
f.Commerce.Price(100, 5000) // 1799.99 (ціна в діапазоні)
f.Commerce.Ean13() // "9780306406157" (штрихкод)
// Finance
f.Finance.Amount(100, 9999) // 4521.67m
f.Finance.Currency().Description // "Ukrainian hryvnia"
f.Finance.CreditCardNumber() // "4111111111111111"
f.Finance.Iban() // "UA573052990000026200335975"
// Lorem (довільний текст)
f.Lorem.Word() // "reprehenderit"
f.Lorem.Words(5) // ["dolor", "sit", "amet", ...]
f.Lorem.Sentence() // "Lorem ipsum dolor sit amet."
f.Lorem.Paragraph(3) // Кілька речень
f.Lorem.Lines(4) // Кілька рядків
// Date
f.Date.Past(years: 5) // Дата у минулому (5 років)
f.Date.Future(years: 1) // Дата майбутньому (1 рік)
f.Date.Recent(days: 30) // За останні 30 днів
f.Date.Between(start, end) // Між двома датами
f.Date.Weekday() // Дата у будній день
f.Date.Month() // "March"
// Random
f.Random.Int(min: 1, max: 100) // Випадкове ціле
f.Random.Decimal(1m, 999.99m) // Випадковий decimal
f.Random.Bool(weight: 0.7f) // true з 70% імовірністю
f.Random.Guid() // Guid.NewGuid()
f.Random.AlphaNumeric(10) // "a7fKp2Lx9R"
f.Random.Enum<OrderStatus>() // Випадкове значення enum
f.PickRandom(list) // Один елемент зі списку
f.PickRandom<T>(list, count: 3) // N елементів зі списку
// Image (placeholder URL)
f.Image.PicsumUrl(width: 400, height: 300) // "https://picsum.photos/400/300"
Реальні додатки мають вкладені структури. Bogus дозволяє будувати повні графи:
public static class SeedFakers
{
// Важливо: Seed забезпечує ОДНАКОВІ дані при кожному запуску
private static readonly int Seed = 12345;
public static Faker<Category> Category = new Faker<Category>("uk")
.UseSeed(Seed)
.RuleFor(c => c.Id, f => f.IndexFaker + 1)
.RuleFor(c => c.Name, f => f.Commerce.Department())
.RuleFor(c => c.Slug, (f, c) => c.Name.ToLower()
.Replace(" ", "-"));
public static Faker<Product> Product = new Faker<Product>("uk")
.UseSeed(Seed)
.RuleFor(p => p.Id, f => f.IndexFaker + 1)
.RuleFor(p => p.Name, f => f.Commerce.ProductName())
.RuleFor(p => p.Description, f => f.Lorem.Paragraph(2))
.RuleFor(p => p.Price, f => f.Finance.Amount(10, 5000))
.RuleFor(p => p.Stock, f => f.Random.Int(0, 500))
.RuleFor(p => p.Sku, f => f.Commerce.Ean13())
.RuleFor(p => p.CategoryId, f => f.Random.Int(1, 10))
.RuleFor(p => p.CreatedAt, f => f.Date.Past(3))
.RuleFor(p => p.IsActive, f => f.Random.Bool(0.9f));
public static Faker<Order> Order = new Faker<Order>("uk")
.UseSeed(Seed)
.RuleFor(o => o.Id, f => f.IndexFaker + 1)
.RuleFor(o => o.CustomerId, f => f.Random.Int(1, 100))
.RuleFor(o => o.Status, f => f.PickRandom<OrderStatus>())
.RuleFor(o => o.CreatedAt, f => f.Date.Past(1))
.RuleFor(o => o.Items, f => new Faker<OrderItem>()
.RuleFor(i => i.ProductId, ff => ff.Random.Int(1, 50))
.RuleFor(i => i.Quantity, ff => ff.Random.Int(1, 10))
.RuleFor(i => i.Price, ff => ff.Finance.Amount(10, 999))
.Generate(f.Random.Int(1, 5))) // 1-5 позицій у замовленні
.FinishWith((f, o) =>
{
// AfterGenerate: обчислюємо Total після генерації Items
o.Total = o.Items.Sum(i => i.Price * i.Quantity);
});
}
Найпрактичніше застосування — заповнення бази даних тестовими даними:
public class DbSeeder
{
private readonly AppDbContext _db;
private readonly ILogger<DbSeeder> _logger;
public DbSeeder(AppDbContext db, ILogger<DbSeeder> logger)
{
_db = db;
_logger = logger;
}
public async Task SeedAsync()
{
if (await _db.Users.AnyAsync())
{
_logger.LogInformation("БД вже містить дані — пропускаємо сідінг.");
return;
}
_logger.LogInformation("Починаємо сідінг БД...");
// Генеруємо категорії
var categories = SeedFakers.Category.Generate(10);
await _db.Categories.AddRangeAsync(categories);
await _db.SaveChangesAsync();
// Генеруємо продукти (пов'язуємо з реальними CategoryId)
var categoryIds = categories.Select(c => c.Id).ToList();
var products = new Faker<Product>("uk")
.UseSeed(12345)
.RuleFor(p => p.Id, f => f.IndexFaker + 1)
.RuleFor(p => p.Name, f => f.Commerce.ProductName())
.RuleFor(p => p.Price, f => f.Finance.Amount(10, 5000))
.RuleFor(p => p.CategoryId, f => f.PickRandom(categoryIds))
.Generate(50);
await _db.Products.AddRangeAsync(products);
await _db.SaveChangesAsync();
_logger.LogInformation(
"Сідінг завершено: {Categories} категорій, {Products} продуктів.",
categories.Count, products.Count);
}
}
using (var scope = app.Services.CreateScope())
{
if (app.Environment.IsDevelopment())
{
var seeder = scope.ServiceProvider.GetRequiredService<DbSeeder>();
await seeder.SeedAsync();
}
}
public class UserServiceTests
{
private readonly Faker<User> _userFaker = new Faker<User>("uk")
.RuleFor(u => u.Id, f => f.Random.Int(1, 10000))
.RuleFor(u => u.FirstName, f => f.Name.FirstName())
.RuleFor(u => u.LastName, f => f.Name.LastName())
.RuleFor(u => u.Email, f => f.Internet.Email());
[Fact]
public async Task CreateUser_WithValidData_ReturnsUser()
{
// Arrange: генеруємо реалістичний запит
var request = new Faker<CreateUserRequest>()
.RuleFor(r => r.FirstName, f => f.Name.FirstName())
.RuleFor(r => r.LastName, f => f.Name.LastName())
.RuleFor(r => r.Email, f => f.Internet.Email())
.RuleFor(r => r.Password, f => f.Internet.Password(12))
.Generate();
// Act
var result = await _service.CreateAsync(request);
// Assert
Assert.True(result.IsError == false);
Assert.Equal(request.Email, result.Value.Email);
}
[Fact]
public async Task GetAll_ReturnsAllUsers()
{
// Генеруємо 25 реалістичних користувачів
var users = _userFaker.Generate(25);
_db.Users.AddRange(users);
await _db.SaveChangesAsync();
var result = await _service.GetAllAsync();
Assert.Equal(25, result.Count);
}
}
[Theory]
[MemberData(nameof(GetInvalidEmails))]
public void Validator_ShouldFail_ForInvalidEmail(string email)
{
var request = new RegisterRequest { Email = email };
var result = _validator.TestValidate(request);
result.ShouldHaveValidationErrorFor(x => x.Email);
}
public static IEnumerable<object[]> GetInvalidEmails()
{
// Комбінуємо Bogus із відомими крайніми випадками
var faker = new Faker();
yield return [faker.Lorem.Word()]; // Просто слово
yield return [faker.Lorem.Sentence()]; // Речення
yield return ["@domain.com"]; // Без local-part
yield return ["user@"]; // Без domain
yield return ["user@.com"]; // Крапка на початку домену
yield return [faker.Random.AlphaNumeric(256) + "@test.com"]; // Завдовгий
}
Bogus підтримує 60+ локалей:
new Faker("uk") // Українська
new Faker("en") // Англійська (за замовчуванням)
new Faker("pl") // Польська
new Faker("de") // Німецька
new Faker("fr") // Французька
new Faker("ja") // Японська
new Faker("zh_CN") // Китайська (спрощена)
new Faker("ar") // Арабська
// Генерація з випадковою локаллю для реального тестування
var locales = new[] { "uk", "en", "pl", "de" };
var faker = new Faker(new Random().Next(locales));
var user = faker.Name.FullName();
Faker<Product> для генерації продуктів: реалістична назва, ціна від 50 до 10000, SKU у форматі "SKU-XXXXX", випадкова категорія з переліку. Згенеруйте 20 продуктів та виведіть у консоль.DbSeeder для проєкту з таблицями: Category (10), Product (50, зв'язані з Categories), User (100), Order (200, зв'язані з Users та Products). Використайте .UseSeed(42) для стабільності.TestBuilders з builder-методами: TestBuilders.AUser(), TestBuilders.AnOrder(), TestBuilders.AProduct(). Кожен метод повертає Faker<T> із базовою конфігурацією, яку можна перевизначити: TestBuilders.AUser().RuleFor(u => u.Role, _ => UserRole.Admin).Generate().Реалістичність
"test123". Виявляють справжні баги.Seed
UseSeed(42) — однакові дані при кожному запуску. Детерміновані тести, стабільна seed-data.Локалізація
Граф об'єктів
FinishWith() та вкладені Faker<T> — генерація складних ієрархічних структур.Посилання:
Генерація PDF з QuestPDF в ASP.NET Core
QuestPDF: layout-based підхід до генерації PDF, компоненти Text, Image, Table, Column/Row, стилізація, динамічні документи та потоковий вивід у ASP.NET Core.
Humanizer та Guard Clauses в ASP.NET Core
Humanizer: перетворення даних у людиночитабельний текст (дати, числа, рядки, переліки). Guard Clauses: захисні перевірки для чистого коду без вкладених if-else.