🏭 Framework-agnostic model factory system for clean testing

Overview

@julr/factorify

Framework-agnostic model factory system for clean testing.

Built-on top of Knex + Faker, and heavily inspired by Adonis.js and Laravel.

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? With Factorify, you can extract all this set up to a dedicated file and then write the bare minimum code to set up the database state.

Features

  • Support for multiple databases ( SQLite, Postgres, MySQL, MSSQL ... )
  • Integrations with test runners
  • Define variations of your model using states
  • Define relations
  • Generate in-memory instances

Table of Contents

Installation

pnpm install @julr/factorify

Integrations

Integrations for some test runners are available below :

Defining configuration and database connection

Before running your tests, you must initialize Factorify with your database configuration.

This must be done BEFORE creating models via Factorify. In general, you can use the setup files system provided by the test runners.

import { defineFactorifyConfig } from '@julr/factorify'

const disconnect = defineFactorifyConfig({ 
  // Can also specify a locale for faker
  locale: 'fr',
  // See https://knexjs.org/guide/#configuration-options
  // for more information
  database: {
    client: 'pg',
    connection: {
      host: 'localhost',
      user: 'root',
      password: 'password',
      database: 'factorify',
    } 
  }
})

// Once you are done with your tests, you can disconnect from the database
await disconnect()

defineFactorifyConfig returns a function that can be used to disconnect from the database.

This is useful when you want to cleanly disconnect from the database after all tests have been run.

Note: You don't need to do this manually if you are using a test runner integration.

Casing Strategy

You can also define a specific casing strategy. By default, Factorify convert all keys to snake_case before inserting the models into the database. And before returning the model, it converts all keys to camelCase.

import { defineFactorifyConfig } from '@julr/factorify'

defineFactorifyConfig({
  casing: {
    // Convert all keys to snake_case before inserting into the database
    insert: 'snake',

    // Convert all keys to camelCase before returning the models
    return: 'camel',
  }
})

Creating factories

import type { User } from './my-user-interface.js'

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

The first parameter must be the table name. Make sure that you return an object with all the required properties, otherwise the database will raise not null exceptions.

Using factories

import { UserFactory } from './my-factory.js'

const user = await UserFactory.create()
const users = await UserFactory.createMany(10)

Merging attributes

You can override the default set of attributes using the .merge method. For example:

await UserFactory
  .merge({ email: '[email protected]' })
  .create()

When creating multiple instances, you can define an array of attributes and they will merge based upon their indices. For example:

await UserFactory
  .merge([
    { email: '[email protected]' },
    { email: '[email protected]' },
  ])
  .createMany(3)

In the above example

  • The first user will have the email of [email protected].
  • The second user will have the email of [email protected].
  • And, the third user will use the default email address, since the merge array has a length of 2.

Factory states

Factory states allow you to define variations of your factories as states. For example: On a Post factory, you can have different states to represent published and draft posts.

export const PostFactory = defineFactory<Partial<Post>>('post', ({ faker }) => ({
    title: faker.lorem.sentence(),
    content: faker.lorem.paragraphs(4),
    status: 'DRAFT',
  }))
  .state('published', (attributes) => ({
    status: 'PUBLISHED', // 👈
  }))
  .build()

By default, all posts will be created with DRAFT status. However, you can explicitly apply the published state to create posts with PUBLISHED status.

await PostFactory.apply('published').createMany(3)
await PostFactory.createMany(3)

Relationships

Model factories makes it super simple to work with relationships. Consider the following example:

export const PostFactory = defineFactory<Partial<Post>>('post', ({ faker }) => ({
    title: faker.lorem.sentence(),
    content: faker.lorem.paragraphs(4),
    status: 'DRAFT',
  }))
  .state('published', (attributes) => ({
    status: 'PUBLISHED', // 👈
  }))
  .build()

export const UserFactory = defineFactory<Partial<User>>('user', ({ faker }) => ({
    username: faker.internet.userName(),
    email: faker.internet.email(),
    password: faker.internet.password(),
  }))
  .hasMany('posts', { foreignKey: 'user_id', localKey: 'id', factory: () => PostFactory }) // 👈
  .build()

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

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

Note that the foreignKey and localKey are optionals. If they are not defined, Factorify will try to guess them based upon the model name.

By default, the foreignKey is {tableName}_id and the localKey is id.

Applying relationship states

You can also apply states on a relationship by passing a callback to the with method.

const user = await UserFactory
  .with('posts', 3, (post) => post.apply('published'))
  .create()

Similarly, if you want, you can create few posts with the published state and few without it.

const user = await UserFactory
  .with('posts', 3, (post) => post.apply('published'))
  .with('posts', 2)
  .create()

user.posts.length // 5

Finally, you can also create nested relationships. For example: Create a user with two posts and five comments for each post.

const user = await UserFactory
  .with('posts', 2, (post) => post.with('comments', 5))
  .create()

The followings are the available relationships:

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

Stubbing models

In some cases, you may prefer to stub out the database calls and just want to create in-memory model instances. This is can achieved using the make and makeMany methods.

const user = await UserFactory
  .with('posts', 2)
  .make()

console.log(user.id)

The make calls will never hit the database and will assign an in-memory numeric id to the model instances.

You might also like...

Javascript-testing-practical-approach-2021-course-v3 - Javascript Testing, a Practical Approach (v3)

Javascript-testing-practical-approach-2021-course-v3 - Javascript Testing, a Practical Approach (v3)

Javascript Testing, a Practical Approach Description This is the reference repository with all the contents and the examples of the "Javascript Testin

Nov 14, 2022

AREX: It is a “Differential Testing” and “Record and Replay Testing” Tool.

AREX: It is a “Differential Testing” and “Record and Replay Testing” Tool. Test restful API by record, replay and stub request/response. Differential

Nov 1, 2022

Placebo, a beautiful new language agnostic diagnostics printer! It won't solve your problems, but you will feel better about them.

Placebo, a beautiful new language agnostic diagnostics printer! It won't solve your problems, but you will feel better about them.

Placebo A beautiful new language agnostic diagnostics printer! ┌─[./README.md] │ 1 │ What is Placebo? · ───┬──── ·

Dec 16, 2022

Hadmean is an internal tool generator. It is language agnostic, schema driven, extremely customizable, featured packed, user-friendly and has just one installation step.

Hadmean is an internal tool generator. It is language agnostic, schema driven, extremely customizable, featured packed, user-friendly and has just one installation step.

Hadmean Report a Bug · Request a Feature · Ask a Question Table of Contents About Quick Demo Motivation Why you should try Hadmean Getting Started Pre

Dec 29, 2022

The project integrates workflow engine, report engine and organization authority management background, which can be applied to the development of OA, HR, CRM, PM and other systems. With tlv8 IDE, business system development, testing and deployment can be realized quickly.

The project integrates workflow engine, report engine and organization authority management background, which can be applied to the development of OA, HR, CRM, PM and other systems. With tlv8 IDE, business system development, testing and deployment can be realized quickly.

介绍 项目集成了工作流引擎、报表引擎和组织机构权限管理后台,可以应用于OA、HR、CRM、PM等系统开发。配合使用tlv8 ide可以快速实现业务系统开发、测试、部署。 后台采用Spring MVC架构简单方便,前端使用流行的layui界面美观大方。 采用组件开发技术,提高系统的灵活性和可扩展性;采

Dec 27, 2022

The invoker based on event model provides an elegant way to call your methods in another container via promisify functions

The invoker based on event model provides an elegant way to call your methods in another container via promisify functions. (like child-processes, iframe, web worker etc).

Dec 29, 2022

Base Rails app that includes login, social login, homepage, and basic model for serving as a scaffold app.

Base Rails app that includes login, social login, homepage, and basic model for serving as a scaffold app.

Rails7Base I created the Rails7Base as a scaffold application. Countless times, I had to create apps that must have the following features: Login syst

Jul 2, 2022

Font-end app to test the transformer model translation from Cape Verdian Creole to English

Getting Started with Create React App This project was bootstrapped with Create React App. Available Scripts In the project directory, you can run: np

Sep 28, 2022

Network physical synchronization model room based on babylonjs + ammojs

Network physical synchronization model room based on babylonjs + ammojs

Network physical synchronization model room based on babylonjs + ammojs The red mesh calculates the physical effects locally of the current user, and

Nov 19, 2022
Releases(v1.0.1)
Owner
Julien Ripouteau
Freelance developer, @adonisjs core member
Julien Ripouteau
Hemsida för personer i Sverige som kan och vill erbjuda boende till människor på flykt

Getting Started with Create React App This project was bootstrapped with Create React App. Available Scripts In the project directory, you can run: np

null 4 May 3, 2022
Kurs-repo för kursen Webbserver och Databaser

Webbserver och databaser This repository is meant for CME students to access exercises and codealongs that happen throughout the course. I hope you wi

null 14 Jan 3, 2023
A testing focused Remix Stack, that integrates E2E & Unit testing with Playwright, Vitest, MSW and Testing Library. Driven by Prisma ORM. Deploys to Fly.io

Live Demo · Twitter A testing focused Remix Stack, that integrates E2E & Unit testing with Playwright, Vitest, MSW and Testing Library. Driven by Pris

Remix Stacks 18 Oct 31, 2022
Moject is a IoC container and an app factory built around the modules idea of Angular and NestJs.

Moject Moject is an IoC container and an app factory package built around the modules idea of Angular and NestJs. Usage npm install moject Use @Mo

Alexander 4 Dec 4, 2022
The Frontend of Escobar's Inventory Management System, Employee Management System, Ordering System, and Income & Expense System

Usage Create an App # with npx $ npx create-nextron-app my-app --example with-javascript # with yarn $ yarn create nextron-app my-app --example with-

Viver Bungag 4 Jan 2, 2023
Personal project to a student schedule classes according his course level. Using GraphQL, Clean Code e Clean Architecture.

classes-scheduler-graphql This is a personal project for student scheduling, with classes according his course level. I intend to make just the backen

Giovanny Lucas 6 Jul 9, 2022
Framework agnostic CLI tool for routes parsing and generation of a type-safe helper for safe route usage. 🗺️ Remix driver included. 🤟

About routes-gen is a framework agnostic CLI tool for routes parsing and generation of a type-safe helper for safe route usage. Think of it as Prisma,

Stratulat Alexandru 192 Jan 2, 2023
Lightweight, fast and framework-agnostic sse service for NodeJS

SSE service Lightweight, fast and framework-agnostic sse service for NodeJS Written on TS. Features Could be embedded to Express, Fastify, Nest, etc.

null 9 Dec 9, 2022
Denail of service system for the Dentistimo system. Used to improve the tolerance and testing fail safe functionality.

Distributed Systems - Dos Testing DoS (Denail of Service) System for Testing and Practical demonstration of systems capability to handle a basic DDoS

null 28 Nov 8, 2022
A personal school project to model the behaviour of the human immune system as a network graph with interactive visualisation.

An educational tool designed to help users understand the immune system. Made using Processing 5 for Java Script

Oscar Bullen 2 Jun 18, 2022