Skip to content
On this page

Defining Factories

A general recomendation is to define your factories in a separate folder. If you applications is quite complex, it is also a good idea to split your factories in multiple files.

Anyway, here is an example of a factory definition:

ts
import type { User } from './types.js'

const UserFactory = defineFactory<User>('user', ({ faker, isStubbed }) => ({
  email: faker.internet.email(), 
  password: faker.random.alphaNumeric(6),
  computedField: () => {
    // You can also use a function to define a field
    return 'computed value'
  }
}))
  .build()

Some things to note here :

  • The first parameter must be the table name of your model.
  • We can pass a generic type to the defineFactory function. This is useful to get autocompletion on the model attributes.
  • Make sure that your factory return an object with all the required properties by your DB, otherwise it will raise not null exceptions.
  • Your factory callback is receiving a faker object, which is a Faker.js instance. You can use it to generate random data.

States

Factory states allow you to define variations of your factories as states. This is useful when you want have multiple variations of your model that you can re-use in your tests

ts
const UserFactory = defineFactory<User>('user', ({ faker }) => ({
  email: faker.internet.email(), 
  password: faker.random.alphaNumeric(6),
  role: 'user'
}))
  .state('admin', () => ({ role: 'admin' }))
  .build()

Here, by default, all the users created with the UserFactory will have the role attribute set to user. But we can also create an admin user by using the admin state:

ts
const admin = await UserFactory.apply('admin').create()

Relationships

Factorify allows you to define relationships between your models. Let's say that we have a Post model that has a userId attribute. We can define a relationship between the User and Post models like this:

ts
const PostFactory = defineFactory<Post>('post', ({ faker }) => ({
  title: faker.lorem.sentence(),
  content: faker.lorem.paragraphs(3)
}))
  .build()

const UserFactory = defineFactory<User>('user', ({ faker }) => ({
  email: faker.internet.email(), 
  password: faker.random.alphaNumeric(6),
  role: 'user'
}))
  .state('admin', () => ({ role: 'admin' }))
  .hasMany('posts', () => PostFactory) // 👈
  .build()

Now, you can create a user and its posts all together in one call.

ts
const user = await UserFactory.with('posts', 3).create()

The followings are the available relationships that works the same way:

  • hasOne
  • hasMany
  • belongsTo
  • manyToMany ( 🚧 coming soon )

Conventions

Factorify supposes that the foreign key of the relationship is the name of the table in snake case followed by _id.

For the above example, Factorify suppose that the Post model has a user_id attribute.

Factorify will also suppose that the local key is id.

If you want to override this convention, you can pass a second parameter to the relationship function:

ts
const PostFactory = defineFactory<Post>('post', ({ faker }) => ({
  title: faker.lorem.sentence(),
  content: faker.lorem.paragraphs(3)
}))
  .build()

const UserFactory = defineFactory<User>('user', ({ faker }) => ({
  email: faker.internet.email(), 
  password: faker.random.alphaNumeric(6),
  role: 'user'
}))
  .state('admin', () => ({ role: 'admin' }))
  .hasMany('posts', () => PostFactory, { 
    localKey: 'my_local_id',
    foreignKey: 'my_fk_user_id'
  })
  .build()

Here, we are saying to Factorify that the local key of User is my_local_id and the foreign key of Post is my_fk_user_id.

Inline relationships

You can also create inline relationships. This allow you to always create a associated model when you create the parent model.

ts
const UserFactory = defineFactory<User>('user', ({ faker }) => ({
  email: faker.internet.email(), 
  password: faker.random.alphaNumeric(6),
})).build()

const AccountFactory = defineFactory<Account>('account', ({ faker }) => ({
  name: faker.company.companyName(),
  userId: () => UserFactory.create()
})).build()

When you create an account, it will also create a user that will be associated to the account.