3
votes

I have an API route with middleware setup in NextJS like so:

/src/middleware/validateData/index.ts

import { NextApiRequest, NextApiResponse } from 'next';
import schema from './schema';

type Handler = (req: NextApiRequest, res: NextApiResponse) => void;

export default (handler: Handler) => {
  return (req: NextApiRequest, res: NextApiResponse) => {
    const { error } = schema.validate(req.body, { abortEarly: false });
    if (error) res.status(400).send(error);
    else handler(req, res);
  };
};

/src/api/foo.ts

import { NextApiRequest, NextApiResponse } from 'next';

import validateData from '../../middleware/validateData';

const foo = (req: NextApiRequest, res: NextApiResponse) => {
  res.send('It works!');
};

export default validateData(foo);

The schema reference is a @hapi/joi schema to validate the req.body data and I haven't included it because I don't think it's relevant to the question.

I'm wondering how I can unit test the middleware on it's own? This is about as far as I got:

/src/middleware/validateData/index.test.ts

import validateData from './validateData';

describe('validateData', () => {
  const mockHandler = jest.fn();

  const mockReq = {
    body: '',
  };

  const mockRes = {
    send: jest.fn(),
  };

  it('responds with error', () => {
    validateData(mockHandler)(mockReq, mockRes);
    expect(mockRes.send).toHaveBeenCalled();
  });
});

But with this technique I firstly get type errors that mockReq and mockRes are missing properties (so I guess I need to mock those correctly but not sure how), and secondly the test fails because res.send is not called despite invalid body data being passed.

Anyone know how to mock and test this correctly?

I feel like my approach is totally wrong because I want to inspect the entire response (status code, specific message received and so on). Is the only approach to spin up a mock server and actually mock an api call or something?

2

2 Answers

0
votes

You can use node-mocks-http package in your case like this

/src/middleware/validateData/index.test.ts

import httpMocks from 'node-mocks-http';
import { NextApiRequest, NextApiResponse } from 'next';
import validateData from './validateData';

describe('validateData', () => {
    const mockHandler = jest.fn();
    const mockReq = httpMocks.createRequest<NextApiRequest>();
    const mockRes = httpMocks.createResponse<NextApiResponse>();

    it('responds with error', () => {
        validateData(mockHandler)(mockReq, mockRes);
        expect(mockRes.send).toHaveBeenCalled();
    });
});
0
votes

next-test-api-route-handler is a package (disclaimer: I created!) that simplifies writing unit tests for Next API routes. It uses test-listen under the hood to generate real HTTP responses. For example:

import validateData from './validateData';
import { testApiHandler } from 'next-test-api-route-handler';

describe('validateData', () => {
  it('responds with error', async () => {
    await testApiHandler({
      handler: validateData((_, res) => res.send('It works!')),
      test: async ({ fetch }) => {
        // Returns a real ServerResponse instance
        const res = await fetch();
        // Hence, res.status == 200 if send(...) was called above
        expect(res.status).toBe(200);
        // We can even inspect the data that was returned
        expect(await res.text()).toBe('It works!');
      }
    });
  });
});

This way, you can also directly examine the fetched response object in your tests. Better, your API route handlers will function identically to how they would in Next.js since they're passed actual NextApiRequest and NextApiResponse instances instead of TypeScript types or mocks.

More examples can be found on GitHub.