Mocka Express-request med Jest och Typescript med korrekta typer


Jag har haft lite problem med att få rätt Express Request-typ att fungera i Jest. Jag har en enkel användarregistrering som fungerar med denna kod:

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 ',
        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')
    );
  });
});

Men om jag ändrar:

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

till :

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

Från TypeScript-dokumentationen (https://www.typescriptlang.org/docs/handbook/utility-types.html#partialt) bör detta göra alla fält på Request-objektet valfria.

Men jag får detta fel:

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.

Jag hoppades att någon med lite mer TypeScript-erfarenhet kunde kommentera och låta mig veta vad jag gör fel.

Svar #1

Din mock-datatyp behöver inte passa den faktiska datan perfekt. Nej, det gör den per definition inte. Det är ju bara en mock, eller hur?

Det du behöver är en typangivelse. Det är ett sätt att säga till TypeScript "Okej bro, jag vet vad jag gör här.".

Detta är inte produktionskod, det är ett test. Du kör det förmodligen till och med i bevakningsläge. Vi kan ignorera viss typkontroll här utan problem. TypeScript vet inte att det är en mock, men det gör vi.

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

Om något kraschar under testet, eftersom mockRequest inte är tillräckligt lik Request, kommer vi att märka det och vi kommer att fixa mocken, lägga till några nya egenskaper etc.

Om as Request inte fungerar kan du säga till TypeScript "Jag VET VERKLIGEN vad jag gör här" genom att först göra en typomvandling till any eller unknown och sedan till den typ du behöver. Det skulle se ut som

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

Det är användbart när vi vill testa att vår kod inte fungerar bra med dålig input.

För ditt särskilda fall -- att mocka express Request -- finns det jest-express som kan hjälpa dig, om du naturligtvis kan avvara storleken på node_modules.

Svar #2

För framtida sökningar om detta tema rekommenderar jag att se detta bibliotek: https://www.npmjs.com/package/node-mocks-http

Detta bibliotek har metoder för att skapa mockade objekt för Request och Response i Express-ramverket, vilket hjälpte mig mycket och var det enkla sätt jag hittade.

Enkel enhetstestexempel:

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();
    });
  });
});

Relaterade artiklar