skip to Main Content

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;

2

Answers


  1. Chosen as BEST ANSWER

    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 :).


  2. The payload should be in the form of the Content-Type set by the code.

    It has two parts to take care.

    Firstly, the content-Type must be specified in the request which has already been done in this case.

    Secondly, whether the payload will automatically be stringified by supertest before posting it. It seems it does not do so. It needs to be done by the code. The post in the citation discusses the same. Along with setting the Content-Type, it stringifies the payload as below.

    const payload = JSON.stringify(data);
    

    Citation:

    Supertest+Jest does not send JSON payload

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search