DEV Community

Cover image for Introducing Factorify - A model factory library for Node.js
Julien Ripouteau
Julien Ripouteau

Posted on

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.

Top comments (1)

Collapse
 
preetamherald profile image
Preetam Yadav

Hey Julian,
Greetings!!

I was recently searching for a model factories library and came across your β€œfactorify” project.
I found it to be really interesting and it almost covers all of my cases.

I had a few questions about the current version and future scopes of the project so i reached out to you.

If we have a parent table User, and a child table ExtendedUser
We want to create a child table on the creation of a parent table. But we need to pass some context to it.
For example, there is a field account type on both tables, so if a parent has account type 1, we want to pass account type 1 while creating the child table

Future scope questions:
Is adding support for the generic foreign key in the scope/timeline?
Is there any particular reason for keeping this sync or are there any plans to move it to an asynchronous manner in the future?