Vincent A Saulys' Blog
Unit Testing in ExpressJS w/ Sequelize & Jest
Tags: javascript programming
July 19, 2021

Unit testing should be to test each function as purely as possible.

This sounds simple but its the most frustrating problem with testing in the NodeJS community! So often people write integration tests when they mean to write unit tests.

This gets particularly gnarly with the express community. Microframeworks like express opt to write a view function sequentially from top-to-bottom. This has the advantage of making it easy at a glance to see what's going on.

app.get("/", async () => {
  // step1
  // step2
  return res.render("myTemplateName", { context, obj });
});

The problem is that each of those steps often unfolds into their own function call. This happens most often with sequelize which will call a database to retrieve an object.

// ./routes/index.js
const { MyObj } = require("../models");

app.get("/", async () => {
  const obj = await MyObj.findByPk(1);
  // step2
  // ...
  return res.render("myTemplateName", { context, obj });
});

The very naieve way to test this is to write on unit test with something like supertest that makes a mock request to the route and checks to see the response

// ./routes/__tests__/app.spec.js
const supertest = require("supertest");
const app = require("../../app");

describe("_", () => {
  it("/", async () => {
    const resp = await supertest(app).get("/");
    expect(resp.statusCode).toBe(200);
    expect(resp.body).toMatch("myVal");
    return;
  })
});

Don't do this!!! -- it is not a unit test

Integration tests like this do have their place in development. It better describes what the user actually experiences.

But such tests can mask where bugs occur at the function call level. You may have a bug where the called object is not returning correctly because sequelize went wrong returning a value or you may have an issue where the express logic for template rendering went wrong. Writing a test like this will mask your issue.

What you should do is separate the two. Mock or fake (there's debate on spies vs mocks vs fakes but its not important here) the sequelize call first -- that will test how express is rendering it. Next, you can write an integration test that relies on a database. If both fail, then you know the issue is in the express rendering. If only the integration test fails, then you know its an issue with the database.

Jest, Sequelize, and Express: less than perfectly happy together

Jest is the preferred testing framework for most nodeJS work both frontend and backend. It works perfectly fine 99% of the time.

But the docs for generate models are awful.When mocking, you don't put in ./__mocks__/models.js nor do you put in ./models/__mocks__/yourObj.js. Instead you need to define a ./models/__mocks__/index.js which will call jest.genMockFromModule("../").

You get a very obtuse error if you define each module in their own file (e.g. ./models/__mocks__user.js):

vincent:testing-nodejs$ npm run test

> testing-nodejs@1.0.0 test
> jest

 FAIL  routes/__tests__/app.spec.js
  ‚óŹ Test suite failed to run

    TypeError: Cannot read property 'name' of undefined

      23 |   .forEach(file => {
      24 |     const model = require(path.join(__dirname, file))(sequelize, Sequelize.DataTypes);
    > 25 |     db[model.name] = model;
         |              ^
      26 |   });
      27 |
      28 | Object.keys(db).forEach(modelName => {

      at forEach (models/index.js:25:14)
          at Array.forEach (<anonymous>)
      at Object.<anonymous> (models/index.js:23:4)
      at Object.<anonymous> (app.js:6:19)

What you actually need to do looks like below

// models/__mocks__/index.js
const models = jest.genMockFromModule("../");

models.thing = {
  findByPk: jest.fn(async () => ({
    name: "love"
  })),
  // solely exists to confirm it was imported correctly
  weirdFunc: jest.fn(() => 42) 
};

module.exports = models;

Then in your test

// routes/__tests__/app.spec.js
const request = require("supertest");

const app = require("../../app");

// if you don't define ./models/__mocks__/index.js, this will
// still work fine -- very misleading!
jest.mock("../../models");
const { thing } = require("../../models");

describe("test", () => {
  it("works", async () => {
    expect(true).toBe(true);
    return;
  });

  it("should give me mocked models", async () => {
    // I like to confirm my testing is working as expected
    // in an almost throwaway unit test -- sort of a "testing
    // the unit testing library" type of approach
    expect(thing.weirdFunc._isMockFunction).toBeTruthy();

    // may help to confirm it returned as you expected
    // const _thing = await thing.findByPk(1);
    // console.log(_thing); 
    return;
  });
});

Your final test, pulling from before the fold, would look something like this:

// ./routes/__tests__/app.spec.js
const supertest = require("supertest");
const app = require("../../app");

describe("_", () => {
  it("/", async () => {
    const resp = await supertest(app).get("/");
    expect(resp.statusCode).toBe(200);
    expect(resp.body).toMatch("myVal");
    return;
  })
});
Share on...