Mocking Express Request with Jest and Typescript using correct types
Asked Answered
S

4

53

I have been having some trouble getting the correct Express Request type working in Jest. I have a simple user registration passing with this code:

import { userRegister } from '../../controllers/user';
import { Request, Response, NextFunction } from 'express';

describe('User Registration', () => {
  test('User has an invalid first name', async () => {
    const mockRequest: any = {
      body: {
        firstName: 'J',
        lastName: 'Doe',
        email: '[email protected]',
        password: 'Abcd1234',
        passwordConfirm: 'Abcd1234',
        company: 'ABC Inc.',
      },
    };

    const mockResponse: any = {
      json: jest.fn(),
      status: jest.fn(),
    };

    const mockNext: NextFunction = jest.fn();

    await userRegister(mockRequest, mockResponse, mockNext);

    expect(mockNext).toHaveBeenCalledTimes(1);
    expect(mockNext).toHaveBeenCalledWith(
      new Error('First name must be between 2 and 50 characters')
    );
  });
});

However, if I change:

    const mockRequest: any = {
      body: {
        firstName: 'J',
        lastName: 'Doe',
        email: '[email protected]',
        password: 'Abcd1234',
        passwordConfirm: 'Abcd1234',
        company: 'ABC Inc.',
      },
    };

to:

const mockRequest: Partial<Request> = {
  body: {
    firstName: 'J',
    lastName: 'Doe',
    email: '[email protected]',
    password: 'Abcd1234',
    passwordConfirm: 'Abcd1234',
    company: 'ABC Inc.',
  },
};

From the TypeScript documentation (https://www.typescriptlang.org/docs/handbook/utility-types.html#partialt), this should make all fields on the Request object optional.

However, I get this error:

Argument of type 'Partial<Request>' is not assignable to parameter of type 'Request'.
  Property '[Symbol.asyncIterator]' is missing in type 'Partial<Request>' but required in type 'Request'.ts(2345)
stream.d.ts(101, 13): '[Symbol.asyncIterator]' is declared here.

I was hoping that someone with a little more TypeScript experience could comment and let me know what I am doing wrong.

Simmie answered 16/9, 2019 at 20:55 Comment(4)
You're right about what Partial<Request> is doing, but I'm betting that the trouble is that userRegister does not accept Partial<Request> - it requires Request right? Are you able to declare mockRequest as Request ? const mockRequest: Partial<Request> ... If not, you could us an assertion: await userRegister(mockRequest as Request, mockResponse, mockNext);Antispasmodic
Right now it looks like this: export const userRegister = async ( req: express.Request, res: express.Response, next: express.NextFunction ) => {. The mock is inside a test. I got around it for now by using: const mockRequest: any = { body: { firstName: 'J', lastName: 'Doe', email: '[email protected]', password: 'Abcd1234', passwordConfirm: 'Abcd1234', company: 'ABC Inc.', }, };. But I was told to try not to use any whenever possible as it defeats the purpose of TypeScript.Simmie
Ahhhh. I see. Yes, userRegister does require req: express.Request and res: express.Response. Interesting, but for testing, is there a way to get around that? I guess I became confused when any worked.Simmie
Can you post the userRegister function here so we can see the type declaration of this function?Coppice
O
92

Your mock data type doesn't have to perfectly fit the actual data. Well, it doesn't by definition. It's just a mock, right?

What you need is a type assertion. It's a way to tell TypeScript "Okay bro, I know what I'm doing here.".

This is not a production code, it's a test. You're probably even running it in watch mode. We can reject some type safety here without problem. TypeScript doesn't know it's a mock, but we do.

const mockRequest = {
    body: {
    firstName: 'J',
    lastName: 'Doe',
    email: '[email protected]',
    password: 'Abcd1234',
    passwordConfirm: 'Abcd1234',
    company: 'ABC Inc.',
    },
} as Request;

If something crashes during the test, because mockRequest isn't similar to Request enough, we'll know and we'll fix the mock, add some new properties etc.

If as Request doesn't work you can tell TypeScript "I REALLY know what I'm doing here" by asserting to any or unknown first and then to the type you need. It would look like

const x: number = "not a number :wink:" as any as number;

It's useful when we'd like to test that our code doesn't work well with bad input.

For your particular case -- mocking express Request -- there is jest-express to help you, if you can spare the node_modules size of course.

Odessaodetta answered 10/1, 2020 at 9:54 Comment(3)
Now when I call the userRegister function though, I get this TS error Argument of type 'Request' is not assignable to parameter of type 'import("/Users/nrb/Development/partsync-server/node_modules/@types/express/index").Request'. Type 'Request' is missing the following properties from type 'Request': get, header, accepts, acceptsCharsets, and 68 more.ts(2345)Simmie
Hey NickB. you have two Requests. One is global and typed in @types/node, the other one is from Express. This is mildly annoying. typescriptlang.org/play/…Odessaodetta
If it still breaks, you can simply replace as Request with as any as Request or simply as any. Thank you so much. Great answer.Abeyant
C
7

For future search about this theme, I recommend seeing this library: https://www.npmjs.com/package/node-mocks-http

This library has methods to create mocked objects for Request and Response of the Express Framework, which helped me a lot and was the easy way I found.

Simple unit test example:

import { Request, Response } from 'express';
import {
  createRequest, createResponse, MockRequest, MockResponse,
} from 'node-mocks-http';
import { AppController } from './app-controller';
import { APP_NAME, APP_VERSION } from '../../constants';

describe('AppController - UnitTestCase', () => {
  let controller: AppController;
  let request: MockRequest<Request>;
  let response: MockResponse<Response>;
  beforeEach(() => {
    controller = new AppController();
    /** Response Mock */
    response = createResponse();
  });

  it('should be defined', () => {
    expect(controller).toBeDefined();
  });

  describe('GET /', () => {
    it('should return 200 and API Name + API Version', (done) => {
      /** Request Mock */
      request = createRequest({
        method: 'GET',
        url: '/',
      });

      AppController.index(request, response);

      const body = { app: `${APP_NAME}:${APP_VERSION}` };
      const result = response._getJSONData();
      expect(result).toMatchObject(body);
      expect(result.app).toEqual(body.app);
      expect(response.getHeaders()).toHaveProperty('content-type');
      console.log('headers', response.getHeaders());
      console.log('response body', result);
      done();
    });
  });
});
Crossbar answered 19/6, 2022 at 6:30 Comment(0)
P
0

Seems like userRegister is the problem as @kschaer said. If you want that function to take in a Partial<Request> you can change userRegister to:

const userRegister = async (req: Partial<Request>, res: Response, next: NextFunction) => { /* snippet */ }

But since this is just for tests you could also cast mockRequest to the Request type like this:

const mockRequest = <Request>{
  body: {
    /* snippet */
  }
};

Hopefully that helps.

Penitential answered 8/1, 2020 at 9:27 Comment(2)
When I do that, I now get this TS error: Type '{ body: { firstName: string; lastName: string; email: string; password: string; passwordConfirm: string; company: string; }; }' is not assignable to type '<Request>() => any'. Object literal may only specify known properties, and 'body' does not exist in type '<Request>() => any'.Simmie
Hmm, not sure then. Sorry.Penitential
G
0

(This is the short version of Anderson Contreira answer).

Use node-mocks-http. It can create expressjs-compatible requests and responses with necessary methods and fields.

How to use:

import { createRequest, createResponse } from 'node-mocks-http';

const req = createRequest();
const res = createResponse();

Modify the request:

req.headers['x-my-header'] = 'test';

Tested with jest and NestJs.

You may find express.request and express.response prototypes, but they do NOT help to create proper objects with all methods, at least in the TypeScript environment.

Gyrostatics answered 3/2 at 13:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.