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 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!