Sequelize Seeders: Generating Test Data

In this article we will look at how to use Sequelize seeders to seed the database with test data and use Faker library to generate the test data. We will continue working in the project travel-api-db-migration-service we created in the video Sequelize Migrations. Please, watch that video if you would like to see how the project was set up.

Below we are going to outline the main steps of creating Sequelize seeders and generating test data with Faker. For more context, please check out the video below.

Sequelize Seeders: Generate Test Data for Your Database with Faker Library

Sequelize Seeders Scripts

First in package.json file of the project, in scripts section let’s add the following scripts. These scripts will call sequelize cli.

"seeder:generate": "sequelize-cli seed:generate",
"seed": "env-cmd sequelize-cli db:seed:all",
"seed:undo": "env-cmd sequelize-cli db:seed:undo",

seeder:generate script will generate a seeder for us in seeders folder. The other two scripts will run and revert the seeders respectively. Since seed and seed:undo scripts need environment variables to connect to the database we use env-cmd to load them from the .env file.

Let’s run seeder generate command

yarn seeder:generate --name test-travels

This command created a seeder with boilerplate code in seeders folder. Like migrations, seeders also have up and down methods. The boilerplate uses bulkInsert and bulkDelete We will come back to the seeder later.

Generate Realistic Data with Faker Library

Now, let’s go ahead and install the package faker-js/faker with the following command

yarn add @faker-js/faker --dev

In seeders folders of the project create factories folder and put file called travelsTestData.js in it. This file will generate test travels for us. Let’s take a look at the code.

const { faker } = require("@faker-js/faker");

function createTours(travelId, number, testSuffix) {
  return (
    Array(number)
      .fill(1)
      .map((n, i) => n + i)
      // eslint-disable-next-line no-unused-vars
      .map((item) => {
        const tourDates = faker.date.betweens({
          from: "2023-11-01",
          to: "2023-11-03",
          count: 2,
        });
        return {
          id: faker.string.uuid(),
          travel_id: travelId,
          name: `${faker.lorem.words(4)} ${testSuffix}`,
          starting_date: tourDates[0],
          ending_date: tourDates[1],
          price: faker.number.float({ min: 10, max: 50, precision: 0.01 }),
        };
      })
  );
}

const testTravelsFactory = function* (number, testSuffix) {
  const array = Array(number)
    .fill(1)
    .map((n, i) => n + i);

  // eslint-disable-next-line no-unused-vars
  for (const item of array) {
    const id = faker.string.uuid();
    yield {
      id,
      name: `${faker.lorem.words(3)} ${testSuffix}`,
      description: faker.lorem.sentence(),
      slug: faker.lorem.slug(4),
      is_public: faker.datatype.boolean(0.7),
      number_of_days: faker.number.int({ min: 2, max: 14 }),
      tours: createTours(id, faker.number.int({ min: 1, max: 5 }), testSuffix),
    };
  }
};

module.exports = {
  testTravelsFactory,
};

testTravelData exports testTravelFactory function. This function is a generator. It takes a number and testSuffix as parameters. number is number of test travels we would like to generate. testSuffix is the suffix we will attach to travel and tour name to identify that it is a test travel or tour. We will also use this suffix to clean up the test data when we undo the seeder.

Let’s look at the code of testTravelFactory. First we create an array out of number . For example, if the number is 3, we create an array of numbers [1, 2, 3] . Then we use the for...of loop to iterate over the array and yield a travel. We use faker’s string.uuid to generate unique id for each travel. Faker’s lorem library generates name, description and slug for a test travel. datatype.boolean gives us true or false value for is_public property with probability of 70% being true. For number_of_days we use number.int between 2 and 14. tours property will be an array of tours generated by createTours function. We will pass travel id so all created tours have the same travel id; faker.number between 1 and 5 so we get between 1 and 5 tours in the array, and testSuffix so we can include it in the name of each tour.

Note, that generating an id before hand for Tours and Travels is possible because we use uuids as primary keys. However, if you are using autoincrementing ids, you have to structure your seeder differently. You first need to create a Travel in the database, and then use returned id from the Travel model to create tours.

Let’s take a look at creatTours function. It also creates an array from the number it is passed. Then it maps over the array and returns a tour for each member of the array. It create the Tour using faker in the similar way a Travel was created. One thing to note is that starting_date and ending_date are generated with faker.date.betweens function so the starting date is before the ending date.

Sequelize Seeders’ Up and Down Functions

Now let’s create code in the seeder file we generated earlier to seed the travels with tours.

"use strict";

const { Op } = require("sequelize");
const { testTravelsFactory } = require("./factories/travelsTestData");

const testSuffix = "**test**";

/** @type {import('sequelize-cli').Migration} */
module.exports = {
  up: async (queryInterface) => {
    for (const travel of testTravelsFactory(100, testSuffix)) {
      const tours = travel.tours;
      delete travel.tours;
      await queryInterface.bulkInsert("travels", [travel]);
      await queryInterface.bulkInsert("tours", tours);
    }
  },

  down: async (queryInterface) => {
    await queryInterface.bulkDelete(
      "tours",
      {
        name: {
          [Op.like]: `%${testSuffix}`,
        },
      },
      {}
    );
    await queryInterface.bulkDelete(
      "travels",
      {
        name: {
          [Op.like]: `%${testSuffix}`,
        },
      },
      {}
    );
  },
};

We import testTravelsFactory and define testSuffix. The up method of the seeder uses for ... of loop to go through 100 travels generated by the testTravelFactory It assigns tours from each travel to a temporary variable tours and deletes tours from the travel. Finally the up method bulk inserts the travel (note that we have to an array out of single travel) and then, it bulk inserts the tours.

The down method reverts bulk inserts by using bulkDelete method on queryInterface. Since tours table has a foreign key constraint travel_id that references id of a travels, the order of deletion is important. First the tours are deleted and then the travels are deleted. bulkDelete method uses like query to delete only tours and travels that contain testSuffix.

Finally let’s go ahead and run the seeder by typing yarn seed command. If we look in the database, we can see test travels and test tours created. They have **test** appended to their names. If we run yarn seed:undo the test data is removed from the database.

Conclusion

In conclusion, Sequelize Seeders emerge as indispensable tools in the realm of database management, offering developers a seamless and efficient way to generate essential test data. By using Sequelize seeders, you can now populate the database with realistic and diverse test scenarios, facilitating thorough testing, and ensuring the robustness of your database-driven projects. As you integrate these practices into your development process, you empower yourself to build more resilient and reliable applications. Happy coding!

Resources

Share this article

Posted

in

by