Models
Eloquent: factories
A factory makes models with realistic attribute values, for your tests and your seeders: one model, or a hundred, with their related models.
Introduction#
Each factory is a class in database/ that describes one model's default attributes. A new app has
UserFactory:
import { Factory, Hash, fake } from '@orieljs/framework'import { User } from '../../app/Models/User'type Attributes = Record<string, unknown>/** The password every factory-made user has, hashed once. */let password: string | undefinedexport default class UserFactory extends Factory<User> { model = User definition() { return { name: fake().name(), email: fake().unique().safeEmail(), email_verified_at: new Date(), password: 'password', remember_token: fake().lexify('??????????'), } } /** Indicate that the model's email address should be unverified. */ unverified() { return this.state({ email_verified_at: null }) } /** Hashes the default password (the model has no hashed cast). */ protected override async attributesFor(overrides: Attributes, index: number): Promise<Attributes> { const attributes = await super.attributesFor(overrides, index) if (attributes.password === 'password') attributes.password = password ??= await Hash.make('password') return attributes }}model names the model, and definition() returns the attributes every model gets, unless you give others. fake()
makes the values: names, email addresses, sentences, numbers and dates.
Factories set every attribute they are given, whatever the model's fillable says.
Defining model factories#
Generating factories#
make:factory writes a factory in database/, named after its model:
node oriel make:factory PostFactoryOriel finds the factories in database/ by itself when your app runs through oriel, as a command or under
oriel serve, and in your tests: every factory class a file there exports. An app started another way, such as with
public/, registers its factories with Factory.register(), or User.factory() throws "No factory registered
for [User].".
A model's factory() returns its factory. To have its own methods, such as unverified(), typed, declare it, as a new
app's User does:
import { Model, type HasFactory } from '@orieljs/framework'import type UserFactory from '../../database/factories/UserFactory'export class User extends Model { declare static factory: HasFactory<UserFactory>}Factory states#
A state changes some attributes. Write it as a method that calls state():
class PostFactory extends Factory<Post> { model = Post definition() { return { title: fake().sentence(), slug: fake().slug(), body: fake().paragraph() } } /** Indicate that the post is published. */ published() { return this.state({ published_at: new Date() }) }}state() also takes a function, which receives the attributes so far and the model's place in the batch:
const users = await User.factory(3) .state((attributes, { index }) => ({ name: `User ${index + 1}` })) .make()"Trashed" state#
trashed() makes soft-deleted models, with a deleted_at date:
const post = await Post.factory().trashed().create()Factory callbacks#
afterMaking() and afterCreating() run a function with each model made or created. Register them in the factory's
configure():
class UserFactory extends Factory<User> { model = User definition() { return { name: fake().name(), email: fake().unique().safeEmail() } } override configure() { return this.afterMaking((user) => { // ... }).afterCreating((user) => { // ... }) }}Creating models using factories#
Instantiating models#
make() makes models without saving them. With count(), or a number given to factory(), it makes a collection: a
plain Collection, where Laravel's is an Eloquent collection, so modelKeys() and load() aren't on it:
const user = await User.factory().make()const users = await User.factory().count(3).make()const others = await User.factory(3).make()Applying states#
const users = await User.factory().count(5).unverified().make()Overriding attributes#
Give make() or create() the attributes to set:
const user = await User.factory().make({ name: 'Abigail Otwell' })Persisting models#
create() makes the models and saves them:
const user = await User.factory().create()const users = await User.factory().count(3).create()makeOne() and createOne() always return one model, typed as one. createMany() takes a number, or a list of
attributes, one model each. raw() returns the attributes without making a model.
Sequences#
sequence() gives the models of a batch attributes in turn, starting again after the last:
const users = await User.factory().count(10).sequence({ name: 'Taylor' }, { name: 'Abigail' }).create()Five users are named Taylor, and five Abigail.
Factory relationships#
Has many relationships#
has() creates related models with each model, through a relation of the model, named after the related model in the
plural unless you name it:
const user = await User.factory().has(Post.factory().count(3)).create()const writer = await User.factory().has(Post.factory().count(3), 'posts').create()Belongs to relationships#
for() sets the parent of the models it makes, a model you have or one its factory creates once for all of them:
const user = await User.factory().createOne()const posts = await Post.factory().count(3).for(user, 'author').create()An attribute of a factory can also be another factory: the factory creates that model, and the attribute takes its
key, as author_id: UserFactory.new() does in a post's definition.
Many to many relationships#
has() works on a many-to-many relation too, and its third argument gives the pivot table's extra columns:
const user = await User.factory().has(Role.factory().count(3), 'roles', { active: 1 }).create()Polymorphic relationships#
has() creates the models of a morphMany() relation, and for() sets the owner of a morphTo() one.
The fake() helper#
fake() returns the generator factories use: a seedable subset of Faker's methods. unique() gives a value that the
generator hasn't given before, and seed() makes the values repeat from one run to the next:
| Kind | Methods |
|---|---|
| People and companies | firstName(), lastName(), name(), userName(), email(), safeEmail(), company(), phoneNumber() |
| Text | word(), words(), sentence(), sentences(), paragraph(), paragraphs(), text(), title(), slug(), url(), city(), country() |
| Numbers | numberBetween(), randomNumber(), randomFloat(), randomDigit(), random() (a number from 0 to 1), boolean(), uuid() |
| Patterns and dates | numerify('###'), lexify('???'), bothify('##??'), hexColor(), dateTime(), dateTimeBetween('-1 year', 'now'), date() |