首页 > 解决方案 > 使用 Jest 在 Nodejs 中进行单元测试

问题描述

我在控制器中有一个方法,如下所示:

    import { calcu } from '../services/myServices';

    export const getProduct = (req, res, next) => {
      try {
        const { type } = req.params;

        const { id, productCode } = req.body;

        if (!id || !productCode) {
          res.status(400).json({ error: { message: 'Id or productCode is required' } });
        } else {
          switch (type.toUpperCase()) {
            case 'X':
              try {
                const result = calcu(id, productCode);
                res.status(200).json(result);
              } catch (err) {
                res.status(400).json({ error: { message: err.message } });
              }
              break;
            default:
              res.status(400).json({ error: { message: `type ${type} is not support` } });
          }
        }
      } catch (err) {
        next(err);
      }
    };

在这种情况下,这是我的单元测试代码:

    import { getProduct } from './quotationController';

    describe('Controller', () => {
        let json, res, status;
        test('Should return message error if the id or productCode is missing', () => {
            const req = {
                body: { id: "1111" },
                param: { type: "qqqqq" }
            };
            const next = err => err.message;
            const result = getProduct(req, res, next);
            //expect(result).toBe(undefined);
            expect(result).toEqual({
                code: 400,
                message: 'Id or productCode is required'
            });
        });
    })

运行单元测试代码时出现错误:

结果未定义。

标签: node.jsunit-testingjestjs

解决方案


这是单元测试解决方案:

controller.js

import { calcu } from './service';

export const getProduct = (req, res, next) => {
  try {
    const { type } = req.params;
    const { id, productCode } = req.body;

    if (!id || !productCode) {
      res.status(400).json({ error: { message: 'Id or productCode is required' } });
    } else {
      switch (type.toUpperCase()) {
        case 'X':
          try {
            const result = calcu(id, productCode);
            res.status(200).json(result);
          } catch (err) {
            res.status(400).json({ error: { message: err.message } });
          }
          break;
        default:
          res.status(400).json({ error: { message: `type ${type} is not support` } });
      }
    }
  } catch (err) {
    next(err);
  }
};

service.js:(模拟)

export function calcu(id, code) {
  return id + code;
}

controller.test.js

import { getProduct } from './controller';
import { calcu } from './service';

jest.mock('./service.js', () => ({ calcu: jest.fn() }));

describe('Controller', () => {
  let mRes;
  let mNext;
  beforeEach(() => {
    mRes = { status: jest.fn().mockReturnThis(), json: jest.fn() };
    mNext = jest.fn();
  });
  afterEach(() => {
    jest.resetAllMocks();
  });

  test('Should return message error if the id or productCode is missing', () => {
    const mReq = { body: { id: '1111' }, params: { type: 'qqqqq' } };
    getProduct(mReq, mRes, mNext);
    expect(mRes.status).toBeCalledWith(400);
    expect(mRes.status().json).toBeCalledWith({ error: { message: 'Id or productCode is required' } });
  });

  test('should call next when error happens', () => {
    const mReq = {};
    getProduct(mReq, mRes, mNext);
    expect(mNext).toBeCalledWith(expect.any(Error));
  });

  test('should return message error if type is not support', () => {
    const mReq = { params: { type: 'qqqqq' }, body: { id: '1111', productCode: '22' } };
    getProduct(mReq, mRes, mNext);
    expect(mRes.status).toBeCalledWith(400);
    expect(mRes.status().json).toBeCalledWith({ error: { message: `type ${mReq.params.type} is not support` } });
  });

  test('should return message error if calcu errors', () => {
    const mReq = { params: { type: 'x' }, body: { id: '1111', productCode: '22' } };
    const mError = new Error('calc error');
    calcu.mockImplementationOnce(() => {
      throw mError;
    });
    getProduct(mReq, mRes, mNext);
    expect(calcu).toBeCalledWith('1111', '22');
    expect(mRes.status).toBeCalledWith(400);
    expect(mRes.status().json).toBeCalledWith({ error: { message: mError.message } });
  });

  test('should return correct calc result', () => {
    const mReq = { params: { type: 'x' }, body: { id: '1111', productCode: '22' } };
    calcu.mockReturnValueOnce({ data: 'fake data' });
    getProduct(mReq, mRes, mNext);
    expect(calcu).toBeCalledWith('1111', '22');
    expect(mRes.status).toBeCalledWith(200);
    expect(mRes.status().json).toBeCalledWith({ data: 'fake data' });
  });
});

覆盖率 100% 的单元测试结果:

 PASS  src/stackoverflow/59508494/controller.test.js (7.379s)
  Controller
    ✓ Should return message error if the id or productCode is missing (6ms)
    ✓ should call next when error happens (1ms)
    ✓ should return message error if type is not support (1ms)
    ✓ should return message error if calcu errors (2ms)
    ✓ should return correct calc result (2ms)

---------------|----------|----------|----------|----------|-------------------|
File           |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
---------------|----------|----------|----------|----------|-------------------|
All files      |      100 |      100 |      100 |      100 |                   |
 controller.js |      100 |      100 |      100 |      100 |                   |
---------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests:       5 passed, 5 total
Snapshots:   0 total
Time:        8.731s, estimated 10s

源代码:https ://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/59508494


推荐阅读