Generate realistic test data with factories
# Create a factory
npx ilana make:factory UserFactory
# Create factory when making model
npx ilana make:model Product --factory
// database/factories/UserFactory.js
const { defineFactory } = require('ilana-orm/orm/Factory');
const { faker } = require('@faker-js/faker');
const User = require('../../models/User');
module.exports = defineFactory(User, () => ({
name: faker.person.fullName(),
email: faker.internet.email(),
password: 'password123',
age: faker.number.int({ min: 18, max: 80 }),
bio: faker.lorem.paragraph(),
is_active: faker.datatype.boolean(),
}));
const User = require('../models/User');
require('../database/factories/UserFactory');
// Create one user
const user = await User.factory().create();
console.log(user.name); // "John Doe" (random name)
// Create user with specific attributes
const admin = await User.factory().create({
role: 'admin',
is_active: true,
});
// Create 10 users
const users = await User.factory().times(10).create();
// Create 5 active users
const activeUsers = await User.factory()
.times(5)
.create({ is_active: true });
// Make user instance without saving to database
const user = await User.factory().make();
console.log(user.name); // Has data but not saved
// Make multiple instances
const users = await User.factory().times(3).make();
// UserFactory.js
const { defineFactory } = require('ilana-orm/orm/Factory');
const { faker } = require('@faker-js/faker');
const User = require('../../models/User');
module.exports = defineFactory(User, () => ({
name: faker.person.fullName(),
email: faker.internet.email(),
password: 'password123',
role: 'user',
is_active: true,
}))
.state('admin', () => ({
role: 'admin',
email: faker.internet.email(),
}))
.state('inactive', () => ({
is_active: false,
deactivated_at: faker.date.recent(),
}))
.state('premium', () => ({
subscription: 'premium',
credits: faker.number.int({ min: 100, max: 1000 }),
}));
// Create admin user
const admin = await User.factory().state('admin').create();
// Create inactive user
const inactiveUser = await User.factory().state('inactive').create();
// Combine multiple states
const premiumAdmin = await User.factory()
.state('admin')
.state('premium')
.create();
// Create multiple users with state
const admins = await User.factory()
.state('admin')
.times(3)
.create();
// UserFactory.js
let userSequence = 1;
Factory.define('User', () => ({
name: faker.name.findName(),
email: `user${userSequence++}@example.com`, // user1@, user2@, etc.
username: `user${userSequence}`,
}));
// Or use Factory.sequence
Factory.define('User', () => ({
name: faker.name.findName(),
email: Factory.sequence(n => `user${n}@example.com`),
username: Factory.sequence(n => `user${n}`),
}));
Factory.define('User', () => ({
name: faker.name.findName(),
email: faker.internet.email(),
password: 'secret123',
}));
// After creating callback
module.exports = defineFactory(User, () => ({
name: faker.person.fullName(),
email: faker.internet.email(),
password: 'secret123',
}))
.afterCreating(async (user) => {
// Create a profile for each user
await user.profile().create({
bio: faker.lorem.paragraph(),
avatar: faker.image.avatar(),
});
})
.afterMaking((user) => {
// Set computed fields
user.display_name = user.name.toUpperCase();
});
// UserFactory.js
Factory.define('User', () => ({
name: faker.name.findName(),
email: faker.internet.email(),
}));
Factory.afterCreating('User', async (user) => {
await user.profile().create({
bio: faker.lorem.paragraph(),
website: faker.internet.url(),
});
});
// Usage
const user = await User.factory().create();
// User will automatically have a profile
// UserFactory.js
Factory.afterCreating('User', async (user) => {
// Create 3-7 posts for each user
const postCount = faker.datatype.number({ min: 3, max: 7 });
for (let i = 0; i < postCount; i++) {
await user.posts().create({
title: faker.lorem.sentence(),
content: faker.lorem.paragraphs(3),
is_published: faker.datatype.boolean(),
});
}
});
// Or use Post factory
Factory.afterCreating('User', async (user) => {
await Post.factory()
.count(faker.datatype.number({ min: 3, max: 7 }))
.create({ user_id: user.id });
});
// PostFactory.js
Factory.define('Post', () => ({
title: faker.lorem.sentence(),
content: faker.lorem.paragraphs(3),
is_published: true,
}));
Factory.afterCreating('Post', async (post) => {
// Attach 1-5 random tags
const tagCount = faker.datatype.number({ min: 1, max: 5 });
const allTags = await Tag.all();
const randomTags = faker.helpers.shuffle(allTags).slice(0, tagCount);
await post.tags().attach(randomTags.map(tag => tag.id));
});
// CommentFactory.js
Factory.define('Comment', () => ({
content: faker.lorem.paragraph(),
user_id: () => User.factory().create().then(user => user.id),
}));
// Create comments for posts
Factory.state('Comment', 'on_post', () => ({
commentable_type: 'Post',
commentable_id: () => Post.factory().create().then(post => post.id),
}));
// Create comments for videos
Factory.state('Comment', 'on_video', () => ({
commentable_type: 'Video',
commentable_id: () => Video.factory().create().then(video => video.id),
}));
// Usage
const postComment = await Comment.factory().state('on_post').create();
const videoComment = await Comment.factory().state('on_video').create();
// ProductFactory.js
Factory.define('Product', () => ({
name: faker.commerce.productName(),
description: faker.commerce.productDescription(),
price: parseFloat(faker.commerce.price()),
sku: faker.random.alphaNumeric(8).toUpperCase(),
stock_quantity: faker.datatype.number({ min: 0, max: 100 }),
is_active: true,
weight: faker.datatype.float({ min: 0.1, max: 10.0, precision: 0.1 }),
dimensions: {
length: faker.datatype.number({ min: 1, max: 50 }),
width: faker.datatype.number({ min: 1, max: 50 }),
height: faker.datatype.number({ min: 1, max: 50 }),
},
}));
Factory.state('Product', 'out_of_stock', () => ({
stock_quantity: 0,
is_active: false,
}));
Factory.state('Product', 'featured', () => ({
is_featured: true,
price: parseFloat(faker.commerce.price(50, 500)), // Higher price range
}));
// PostFactory.js
Factory.define('Post', () => ({
title: faker.lorem.sentence().replace('.', ''),
slug: faker.helpers.slugify(faker.lorem.sentence()).toLowerCase(),
excerpt: faker.lorem.sentences(2),
content: faker.lorem.paragraphs(5, '\n\n'),
featured_image: faker.image.imageUrl(800, 600, 'business'),
is_published: faker.datatype.boolean(),
published_at: faker.date.recent(30),
meta_title: faker.lorem.sentence(),
meta_description: faker.lorem.sentences(2),
reading_time: faker.datatype.number({ min: 2, max: 15 }),
view_count: faker.datatype.number({ min: 0, max: 10000 }),
}));
Factory.state('Post', 'published', () => ({
is_published: true,
published_at: faker.date.recent(30),
}));
Factory.state('Post', 'draft', () => ({
is_published: false,
published_at: null,
}));
Factory.state('Post', 'popular', () => ({
view_count: faker.datatype.number({ min: 1000, max: 50000 }),
is_featured: true,
}));
// tests/UserTest.js
const User = require('../models/User');
require('../database/factories/UserFactory');
describe('User Model', () => {
test('can create user', async () => {
const user = await User.factory().create();
expect(user.id).toBeDefined();
expect(user.name).toBeDefined();
expect(user.email).toContain('@');
});
test('admin users have admin role', async () => {
const admin = await User.factory().state('admin').create();
expect(admin.role).toBe('admin');
});
test('can create multiple users', async () => {
const users = await User.factory().count(5).create();
expect(users).toHaveLength(5);
expect(users[0].name).toBeDefined();
});
});
// tests/PostCreationTest.js
describe('Post Creation', () => {
test('user can create post', async () => {
const user = await User.factory().create();
const post = await Post.factory().create({
user_id: user.id,
title: 'Test Post',
});
expect(post.user_id).toBe(user.id);
expect(post.title).toBe('Test Post');
// Test relationship
await post.load('author');
expect(post.author.id).toBe(user.id);
});
});
// database/seeds/DemoSeeder.js
const Seeder = require('ilana-orm/orm/Seeder');
const User = require('../../models/User');
const Post = require('../../models/Post');
const { faker } = require('@faker-js/faker');
class DemoSeeder extends Seeder {
async run() {
// Clear existing data
await Post.query().delete();
await User.query().delete();
// Create demo users
const admin = await User.factory().state('admin').create({
name: 'Demo Admin',
email: 'admin@demo.com',
});
const users = await User.factory().times(10).create();
// Create posts for each user
for (const user of [admin, ...users]) {
await Post.factory()
.times(faker.number.int({ min: 2, max: 8 }))
.create({ user_id: user.id });
}
console.log('Demo data created successfully!');
}
}
module.exports = DemoSeeder;
// Custom faker methods
const customFaker = {
...faker,
custom: {
username: () => {
return faker.internet.userName().toLowerCase().replace(/[^a-z0-9]/g, '');
},
phoneNumber: () => {
return faker.phone.phoneNumber('###-###-####');
},
productCategory: () => {
const categories = ['Electronics', 'Clothing', 'Books', 'Home', 'Sports'];
return faker.helpers.randomize(categories);
},
blogStatus: () => {
return faker.helpers.randomize(['draft', 'published', 'archived']);
},
},
};
// Use in factory
Factory.define('User', () => ({
name: faker.name.findName(),
username: customFaker.custom.username(),
phone: customFaker.custom.phoneNumber(),
}));
// Set faker locale
faker.locale = 'es'; // Spanish
// faker.locale = 'fr'; // French
// faker.locale = 'de'; // German
Factory.define('User', () => ({
name: faker.name.findName(), // Will generate Spanish names
address: faker.address.streetAddress(),
city: faker.address.city(),
}));
// Efficient bulk creation
async function createBulkUsers(count) {
const users = [];
// Generate data first
for (let i = 0; i < count; i++) {
users.push({
name: faker.name.findName(),
email: faker.internet.email(),
password: 'secret123',
created_at: new Date(),
updated_at: new Date(),
});
}
// Insert in batches
const batchSize = 100;
for (let i = 0; i < users.length; i += batchSize) {
const batch = users.slice(i, i + batchSize);
await User.query().insert(batch);
}
return users;
}
// Cache frequently used data
let cachedCategories = null;
Factory.define('Post', async () => {
// Reuse categories instead of creating new ones
if (!cachedCategories) {
cachedCategories = await Category.factory().count(5).create();
}
return {
title: faker.lorem.sentence(),
content: faker.lorem.paragraphs(3),
category_id: faker.helpers.randomize(cachedCategories).id,
};
});
// ✅ Good - simple and focused
Factory.define('User', () => ({
name: faker.name.findName(),
email: faker.internet.email(),
password: 'secret123',
}));
// ❌ Bad - too complex
Factory.define('User', () => ({
name: faker.name.findName(),
email: faker.internet.email(),
password: bcrypt.hashSync('secret123', 10), // Expensive operation
profile: { // Nested complexity
bio: faker.lorem.paragraph(),
avatar: faker.image.avatar(),
},
}));
// ✅ Good - predictable for tests
Factory.define('User', () => ({
name: faker.name.findName(),
email: faker.internet.email(),
password: 'secret123', // Same password for all test users
}));
// ❌ Bad - unpredictable
Factory.define('User', () => ({
password: faker.internet.password(), // Different every time
}));
database/factories/
├── UserFactory.js
├── PostFactory.js
├── CommentFactory.js
├── ecommerce/
│ ├── ProductFactory.js
│ ├── OrderFactory.js
│ └── CategoryFactory.js
└── blog/
├── ArticleFactory.js
└── TagFactory.js
/**
* User Factory
*
* States:
* - admin: Creates user with admin role
* - inactive: Creates deactivated user
* - premium: Creates user with premium subscription
* - verified: Creates user with verified email
*/
Factory.define('User', () => ({
// ... factory definition
}));