ExpressJS and Sequelize application tested with mocha, chai, supertest, migrations and seeds

Sebastián Pérez Etchandy
Nowports Tech and Product
6 min readMar 16, 2021
Image from https://undraw.co/

In this demo, we will explore one way to test your expressJS + Sequelize application without risking your database, and making sure that you have enough test data to cover not only common test cases, but also edge test cases.

Initializing Sequelize + ExpressJS application

Firstly, we will start by creating a new Sequelize project. In this example I’ll be using Node v14.15.0, and MySQL 5.7.29, but this solution could perfectly work with any database.

We will start by installing and initializing Sequelize, ExpressJS and sequelize-cli as the core tools for developing the application.

npm init
npm install --save sequelize
npm install --save mysql2
npm install --save express

In addition, we will install mocha and chai tools for testing and assertion, and supertest as one of the most frequently used options to test express applications.

npm install --save mocha
npm install --save supertest
npm install --save chai

Then, we will install some extra useful packages related to configurations. One of them is dotenv (used for loading information from an .ENV file by accessing process.env), and the other is cross-env (used for helping us to set the NODE_ENV in any command line, without worrying about the operating system). Another very useful package to install is the body-parser, a middleware used with express to get request’s body as an Object.

npm install --save dotenv
npm install --save-dev cross-env
npm install --save-dev body-parser

After all needed packages are installed, we will initialize the application by running:

npx sequelize-cli init

Database configuration

Now that we have all needed dependencies installed, let’s proceed to create and configure the database connection. For this example, we will be setting three database configurations: one for production, other for our development environment, and another that will be dynamically created and populated each time you run your tests.

Why are we writing development and testing configuration? Could they not be the same? In my experience doing unit tests, I realized that when I run unit tests, I don’t want to lose my development data because if I use the same database for testing and development, I’ll probably start to lose control of my data. As a developer, I like to keep my data (even in a development environment) secure.

After initializing your app with sequelize-cli, you will find that there is a file called config/config.json. We will edit this file changing its name to config/config.js. Then we will set our configuration similar to the following example:

require('dotenv').config();module.exports = {
development: {
username: process.env.MYSQL_DEV_USER,
password: process.env.MYSQL_DEV_PASSWORD,
database: process.env.MYSQL_DEV_DB_NAME,
host: process.env.MYSQL_DEV_HOST,
dialect: 'mysql',
},
test: {
username: process.env.MYSQL_TEST_USER,
password: process.env.MYSQL_TEST_PASSWORD,
database: process.env.MYSQL_TEST_DB_NAME,
host: process.env.MYSQL_TEST_HOST,
port: process.env.MYSQL_TEST_PORT,
dialect: 'mysql',
},
production: {
username: process.env.MYSQL_USER,
password: process.env.MYSQL_PASSWORD,
database: process.env.MYSQL_DB_NAME,
host: process.env.MYSQL_HOST,
dialect: 'mysql',
},
};

To use this config file as a javascript file instead of as a JSON, we need to update models/index.js file, changing the config file loaded when the const config is defined .
const config = require(__dirname + ‘/../config/config.js’)[env];

We can now create our database with sequelize-cli.

npx sequelize-cli db:create

Creating models

For this example, we will imagine that we are creating products for an inventory application. This products will have name, price, stock and minStock. After defining our model, we will run the migration to create this table on the database.

npx sequelize model:generate --name Product --attributes name:string,price:double,stock:integer

After running this command, you will see two new files in your project, one for the migration and one for the model. Now we will run the migration in order to create our products table in the dev database.

npx sequelize-cli db:migrate

I recommend you to add some products directly from your database, in order to have some values to difference the ones from development with the ones from testing.

Create basic endpoint

Now that we have our database created, we will create a simple endpoint to fetching products, and also get an alert with the value true if the stock is less than the minStock value.

For doing this, first we will create three important files, app.js as our entry point to the system, a router file, and a controller (in some software architectures, a controller is an entity in charge of interacting between human actions and business logic). Create the following files:

  1. routes/index.js
  2. app.js
  3. controllers/products.js

If everything it’s OK, you should check the result of this in your browser, or with any tool like postman or insomnia. In order to do that, just make a GET request to http://localhost:3030/products. You should get something like this (as I recommended you before, I added one product directly in my dev database):

{
"status": "success",
"data": [
{
"id": 1,
"name": "Item 1",
"price": 100,
"stock": 20,
"minStock": 10,
"createdAt": "2021-02-08T16:50:26.000Z",
"updatedAt": "2021-02-08T16:50:26.000Z",
"shouldRestock": false
}
]
}

Writing unit tests with seeders

So, what happens now if we want to check if the shouldRestock alert works, but we don’t want to change anything in our development database, because we want to preserve the data as it is? To solve this problem, we use sequelize seeds with a testing database, only for unit tests.

Let’s start by creating the testing database.

npx cross-env NODE_ENV=test npx sequelize-cli db:create

Now, we will write one seeder to populate the Products table with useful data.

npx sequelize-cli seed:generate --name test-products

After running this command, a new file will be created in the seeders/ directory. We will then move it to seeders/test in order to keep separated places for each kind of seeder.

Now let’s write a simple seeder. In this case, we need three different products to cover all stock cases:

  1. Product A with more than minStock
  2. Product B with less than minStock
  3. Product C with the same value of minStock.

The advantage of having full control in our data is that we can reach full coverage, because we designed our data exactly as we need it to test our app.

Now let’s write a simple test, starting with a new file in test/controller/products.test.js. In the following example, we will create three different objects with the expected data, and our test case will expect the response to contain the three mentioned objects (using expect from mocha).

Now again that we have our test written, the final step is to configure the test script. This script should have the following behavior:

  1. Drop previous testing database
  2. Create new testing database
  3. Seed testing database
  4. Run tests

For doing this, we will write this commands inside the scripts section in package.json file.

"test": "cross-env NODE_ENV=test mocha ./test/*",
"pretest": "cross-env NODE_ENV=test npm run db:reset",
"db:reset": "npx sequelize-cli db:drop && npx sequelize-cli db:create && npx sequelize-cli db:migrate && npx sequelize-cli db:seed:all --seeders-path ./seeders/test",
"db:create:test": "cross-env NODE_ENV=test npx sequelize-cli db:create"

Run your tests

Now, we can finally run npm test and test if our test is working correctly. If everything goes well, you should get a response like this.

Sequelize CLI [Node: 14.15.0, CLI: 6.2.0, ORM: 6.5.0]Loaded configuration file "config/config.js".
Using environment "test".
Database products_test dropped.
Sequelize CLI [Node: 14.15.0, CLI: 6.2.0, ORM: 6.5.0]Loaded configuration file "config/config.js".
Using environment "test".
Database products_test created.
Sequelize CLI [Node: 14.15.0, CLI: 6.2.0, ORM: 6.5.0]Loaded configuration file "config/config.js".
Using environment "test".
== 20210205141729-create-product: migrating =======
== 20210205141729-create-product: migrated (0.031s)
Sequelize CLI [Node: 14.15.0, CLI: 6.2.0, ORM: 6.5.0]Loaded configuration file "config/config.js".
Using environment "test".
== 20210208203854-test-products: migrating =======
== 20210208203854-test-products: migrated (0.010s)
> testing_express_sequelize@1.0.0 test /Users/seba/Documents/Teaching/testing_express_sequelize
> cross-env NODE_ENV=test mocha ./test/*
Listening on port: 3030Fetch products test
Executing (default): SELECT `id`, `name`, `price`, `stock`, `minStock`, `createdAt`, `updatedAt` FROM `Products` AS `Product`;
✓ Shows all stock states (74ms)
1 passing (77ms)

Conclusion

After following this demo, now you have an express application with unit test and custom seeded data. From this example, you can now start creating your own unit tests with seeders. If you want to see the entire project, you can find it on Github.

--

--

Sebastián Pérez Etchandy
Nowports Tech and Product

I'm a passionated programmer who loves to learn about almost everything. I like mainly football, video games, music and spending time with my friends.