Introducing Factorify - A model factory library for Node.js
Tags: node, testing
Published: 2022-09-25 17:20:43

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mv1x5tdow07a8w8peuwq.png)

---

If you have already worked with [Adonis.js](https://adonisjs.com/), 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: 

```ts
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()
```

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. 

```ts
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 🎉
})
```

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 :

```ts
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)
})
```

- Attributes overriding, which allows you to override the attributes of a model, without having to redefine the whole factory :

```ts
// 👇 We override the name attribute of the user
const user = await UserFactory.merge({ name: 'Julien' }).create()
```

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.