Introducing Factorify - A model factory library for Node.js

julienr

Julien Ripouteau

Posted on September 25, 2022

Introducing Factorify - A model factory library for Node.js

Have you ever written tests, in which the first 15-20 lines of each test are dedicated to just setting up the database state by using multiple models?

That's the problem Factorify is trying to solve
Factorify example


If you have already worked with Adonis.js, or Laravel, the concept of model factory must be very familiar to you. The idea here is exactly the same, Factorify is totally inspired by that.

I built Factorify because I quickly realized that Model Factories was a tool that I was really missing to do my tests while I was working on a different stack ( The current stack I have to work on is Hasura / AWS Lambda ).

Most of the time using Factorify is relevant when you are working on a stack without ORM, or if your ORM does not offer an equivalent feature. But any good ORM must offer a more or less similar system :

For example MikroORM, which is the ORM for the cool kids right now, offers factories: https://mikro-orm.io/docs/seeding#entity-factories

Prisma ORM doesn't seem to have a builtin solution, but there are third party plugins for : https://github.com/echobind/prisma-factory

Lucid, which is the ORM of Adonis.js, also have model factories : https://docs.adonisjs.com/guides/models/factories

etc.


Factorify is very, very simple to use. I will skip the database configuration step for keeping the post short, but you can find more information on the documentation website : https://factorify.julr.dev/

The first step is to simply define factors for each of your models. Here we will work on a database that has three models, users, and posts, and comments.
A user can have several posts, and a post can have several comments.

We start by defining the factories, with the relationships they have between them:

export const CommentFactory = defineFactory('comment', ({ faker }) => ({
  content: faker.lorem.paragraph(),
}))
  .build()

export const PostFactory = defineFactory<>('post', ({ faker }) => ({
  title: faker.lorem.sentence(),
}))
  // πŸ‘‡ We define a relationship between the post and the comment model
  .hasMany('comments', () => CommentFactory)
  .build()

export const UserFactory = defineFactory<any>('user', ({ faker }) => ({
  id: faker.datatype.number(),
  name: faker.name.fullName(),
  role: 'user'
}))
  .state('admin', () => ({ role: 'admin' }))
  // πŸ‘‡ We define a relationship between the user and the post model
  .hasMany('posts', () => PostFactory)
  .build()
Enter fullscreen mode Exit fullscreen mode

And that's it! We are ready to write tests that are clean.
In any of our tests, we can now use the factories to create the database state we need.

import { UserFactory } from '../factories.js'

test('My test ', () => {
  // πŸ‘‡ We create 3 users, each with 2 posts, and each post with 3 comments
  const user = await UserFactory
    .with('posts', 2, posts => posts.with('comments', 3))
    .createMany(3)

  // πŸ‘‡ We can also create a single admin user by applying the previously defined state
  const admin = await UserFactory.apply('admin')
    .with('posts', 4)
    .create()

  // Now you can focus on testing your business logic, without having to write
  // 15 lines of code to create the database state you need πŸŽ‰
})
Enter fullscreen mode Exit fullscreen mode

And there you go. This makes test writing much easier and cleaner !

Factorify hides a lot of other nice features, like :

  • Stubbing, which allows you not to persist anything in the database and just return the model to memory. Useful for testing an API : you just generate the model and store it in a variable, you send it to your API with supertest or whatever :
test('Should insert user', () => {
  // πŸ‘‡ Calling `make` or `makeMany` will generate the model in memory, without persisting it in the database
  const user = await UserFactory.make()

  request(app).post('/users').send(user).expect(200)
})
Enter fullscreen mode Exit fullscreen mode
  • Attributes overriding, which allows you to override the attributes of a model, without having to redefine the whole factory :
// πŸ‘‡ We override the name attribute of the user
const user = await UserFactory.merge({ name: 'Julien' }).create()
Enter fullscreen mode Exit fullscreen mode

And some other features that you can find on the documentation website : https://factorify.julr.dev/


I hope you will find Factorify useful, and if you have any feedback, please let me know ! I am also open to any PRs, if you want to contribute to the project.

You can find the project here on GitHub : https://github.com/julien-r44/factorify/
Please show some πŸ’– and star the project if you like it, it will help me a lot to get more visibility on the project.

πŸ’– πŸ’ͺ πŸ™… 🚩
julienr
Julien Ripouteau

Posted on September 25, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related