javascriptnode.jsexpressunit-testingvitest

supertest request body is undefined


I want to test my express server POST endpoint using supertest. Everything works fine in postman, but when I try to pass in body parameters into the test, as can be seen in the code snippets below, it appears that the body parameters don't get passed in correctly. I get request body undefined.

Code from my controller.ts

import { Router } from 'express';
import { z } from 'zod';
import { Kysely } from 'kysely';
import * as sprints from './services';
import * as schema from './schema';
import { DB } from '../../database';

export function createSprintsRouter(db: Kysely<DB>) {
  const router = Router();

  router.post('/', async (req, res) => {
    try {
      const parsedInput = schema.parseNewSprintInput.parse(req.body);
      const newSprint = await sprints.createSprint(db, parsedInput);
      res.status(201).json(newSprint);
    } catch (err) {
      if (err instanceof z.ZodError) {
        res.status(400).json({ err: err.errors });
      } else {
        res.status(500).json({ err: (err as Error).message });
      }
    }
  });

  return router;
}

Code from my test file controller.spec.ts

import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import supertest from 'supertest';
import express from 'express';
import { Kysely } from 'kysely';
import { createSprintsRouter } from '../controller';
import { getTestDbInstance } from '../../../database/db-test';
import type { DB } from '@/database/types';

describe('Sprints Controller', () => {
  let testDb: Kysely<DB>;
  let app: express.Application;

  beforeAll(async () => {
    testDb = getTestDbInstance();
    const sprintsRouter = createSprintsRouter(testDb);
    app = express().use('/sprints', sprintsRouter);

    await testDb
      .insertInto('sprints')
      .values([
        { code: 'TST1', title: 'Test Sprint 1' },
        { code: 'TST2', title: 'Test Sprint 2' },
      ])
      .execute();
  });

  afterAll(async () => {
    await testDb.destroy();
  });

  describe("POST '/' endpoint", () => {
    it('should create a new sprint', async () => {
      const newSprint = { code: 'TST3', title: 'Test Sprint 3' };

      const res = await supertest(app)
        .post('/sprints')
        .set('Content-Type', 'application/json')
        .send(newSprint);

      expect(res.status).toBe(201);
      expect(res.body).toEqual(
        expect.objectContaining({
          code: 'TST3',
          title: 'Test Sprint 3',
        })
      );
      const allSprints = await supertest(app).get('/sprints');
      expect(allSprints.body).toHaveLength(3);
    });
  });
});

I've searched solutions to this problem and most of the time it comes up that you have to use app.use(bodyParser.json()); before the routes (which I'm using). But the problem still persist.

My main app.ts

import express from 'express';
import './bot/discordBot';
import messages from './modules/messages/controller';
import messageTemplates from './modules/message-templates/controller';
import db from './database/index';
import { createSprintsRouter } from './modules/sprints/controller';

const app = express();
app.use(express.json());
const sprintsRouter = createSprintsRouter(db);

app.use('/messages', messages);
app.use('/templates', messageTemplates);
app.use('/sprints', sprintsRouter);

export default app;

Solution

  • The problem was that I wasn't using express express.json() in my TEST file controller.spec.ts. I'm still learning to code so I might be techincally incorrect, but as I understand express.json() middleware helps to parse json body which we send via request to the endpoint. Since I have not implemented it into my TEST file, that's why my req.body was always undefined (as it was not parsed appropriately). I have implemented this middleware in my main file app.ts, thus I thought that I'm using it everywhere, but as I understand, my test file is using separate instance of express, thus it had different settings from my main file. So to sum up, make sure that you use express.json() middleware in your setup :).