Extending Fishery to build test data for Sequelize models

28 August 2020 by Tolga Paksoy

If you have worked with Rails in the past, you know how nice it is to use Faker and FactoryBot in your specs. These two libraries work together flawlessly and makes writing test data a breeze. Thoughtbot, the creators of FactoryBot, also have a similar library for Node: Fishery.

There is a downside though: the docs only specify building a plain Javascript object. If you would like your factories to build and create Sequelize models, some ingenuity is required. Lets immediately skip to how we define factories for Sequelize models.

A class mixin to wrap your Factory classes with the required Sequelize logic:

// tests/sequelize-factory-mixin.js
import { Factory } from "fishery"

export const SequelizeFactoryMixin = model => (
  class extends Factory {
    build(params, options) {
      const attributes = super.build(params, options)
      return model.build(attributes)
    }

    async create(params, options) {
      return await this.build(params, options).save()
    }
  }
)

Given a small Sequelize model called Product:

// server/db/models/product.js
import { Model } from "sequelize"

export class Product extends Model {
  static init(sequelize, DataTypes) {
    return super.init({
      // ... other database field definitions
      title: { type:DataTypes.STRING },
      tags: { type: DataTypes.ARRAY(DataTypes.STRING) },
      // ... other database field definitions
    }, {
      sequelize: sequelize,
      modelName: "Product",
      tableName: "products"
    })
  }
}

This is how we would use our SequelizeFactoryMixin:

// tests/factories/products.js
import { SequelizeFactoryMixin } from "../sequelize-factory-mixin.js"
import { Product } from "../../server/db/models/product.js"

class ProductFactory extends SequelizeFactoryMixin(Product) {
  // Creating a class for our product factory allows us to add our traits here
  featured() {
    return this.params({ tags: ["Featured"] })
  }
}

export const productFactory = ProductFactory.define(({ sequence, params }) => {
  return {
    id: sequence,
    title: params.title || "Default Title",
    tags: params.tags || []
  }
})

And in our Jest test suite, this would allow us to create our Sequelize records:

// tests/server/db/models/product.test.js
import { productFactory } from "../../../factories/products.js"
import { db } from "../../../../server/db"

describe("Product", () => {
  describe("title", () => {
    let productCreatedWithFactory

    beforeAll(async () => {
      // Use `featured` trait defined in `class ProductFactory` and save the product.
      productCreatedWithFactory = await productFactory.featured().create({
        title: "Title override"
      })
    })

    it("should be saved in database", async () => {
      const dbProduct = await db.Product.findByPk(productCreatedWithFactory.id)
      expect(dbProduct.title).toEqual(productCreatedWithFactory.title)
    })
  })
})