首页 > 解决方案 > 开玩笑测试nodejs控制器

问题描述

我有以下控制器

import bcrypt from 'bcrypt';
import jwt from 'jsonwebtoken';
import { UserModel, isPasswordAllowed } from '../../models/User';

const saltRounds = 10;

function userController() {
  function add(req, res) {
    try {
      if (req.body.administrator) {
        res.status(400).json({
          error: {
            message: 'Bad Request',
          },
        });
        return;
      }

      if (!isPasswordAllowed(req.body.password)) {
        res.status(400).json({
          error: {
            message: 'La contraseña no cumple con los requisitos minimos',
          },
        });
        return;
      }

      bcrypt.hash(req.body.password, saltRounds, async (err, hash) => {
        if (err) {
          res.status(500).json({ error: { code: '500', message: err.errmsg } });
          return;
        }
        const user = new UserModel();
        user.email = req.body.email.toLowerCase();
        user.password = hash;

        await user
          .save()
          .then(() => {
            const token = jwt.sign(
              {
                username: user.email,
                userId: user.id,
              },
              process.env.JWT_KEY,
              {
                expiresIn: '7d',
              },
            );

            res.status(200).json({
              message: 'Usuario Creado',
              token,
              email: user.email,
            });
          })
          .catch((error) => {
            if (error.code === 11000) {
              res.status(400).json({
                error: { code: '500', message: 'El correo ya existe' },
              });
            } else {
              console.log(error);
              res.status(500).json({ error: { code: '500', message: error.message } });
            }
          });
      });
    } catch (error) {
      res.status(503).json({ error });
    }
  }
  return {
    add,
  };
}

export default userController();

正如您所料,这个控制器工作得很好,用户是在数据库中创建的,但我有以下测试:

import UserController from './UserController';
import { connect, closeDatabase, clearDatabase } from '../../__test__/db-handler';

describe('test UserController', () => {
  const res = {};
  beforeEach(async () => {
    await connect();
    res.send = jest.fn().mockReturnValue(res);
    res.status = jest.fn().mockReturnValue(res);
    res.json = jest.fn().mockReturnValue(res);
  });

  afterEach(async () => {
    await clearDatabase();
  });

  afterAll(async () => {
    await closeDatabase();
  });

  test('should return the expect api method', () => {
    const userControllerApi = {
      add: expect.any(Function),
    };

    expect(UserController).toMatchObject(userControllerApi);
  });

  test('should return 400 error bad request is body contains administrator: true', async () => {
    const req = {
      body: {
        administrator: true,
      },
    };

    await UserController.add(req, res);
    expect(res.status).toHaveBeenCalledWith(400);
    expect(res.json).toHaveBeenCalledTimes(1);
    expect(res.json).toHaveBeenCalledWith({
      error: {
        message: 'Bad Request',
      },
    });
  });

  test('should return 400 error bad request is password is not allow', async () => {
    const req = {
      body: {
        password: '123456',
      },
    };
    await UserController.add(req, res);

    expect(res.status).toHaveBeenCalledWith(400);
    expect(res.json).toHaveBeenCalledTimes(1);
    expect(res.json).toHaveBeenCalledWith({
      error: {
        message: 'La contraseña no cumple con los requisitos minimos',
      },
    });
  });
  
  // this test is not passing
  test('should create an user and return a token', async () => {

    const req = {
      body: {
        email: 'test@test.com',
        password: 'Abc123456',
      },
    };

    const expectObject = {
      message: 'Usuario Creado',
      email: 'test@test.com',
    };

    await UserController.add(req, res);

    jest.useFakeTimers();

    expect(res.status).toHaveBeenCalledWith(200);
    expect(res.json).toHaveBeenCalledTimes(1);
    expect(res.json).toMatchObject(expectObject);
  });
});

但最后一个测试“应该创建一个用户并返回一个令牌”从未通过,我得到以下信息:

  ● test UserController › should create an user and return a token

    expect(jest.fn()).toHaveBeenCalledWith(...expected)

    Expected: 200

    Number of calls: 0

      78 |     jest.useFakeTimers();
      79 | 
    > 80 |     expect(res.status).toHaveBeenCalledWith(200);
         |                        ^
      81 |     expect(res.json).toHaveBeenCalledTimes(1);
      82 |     expect(res.json).toMatchObject(expectObject);
      83 |   });

我还在测试模式下调试了这段代码,如下图所示,代码输入在 中res.status(200).json({ .... }),所以我不明白这里发生了什么。

标签: javascriptnode.jstestingjestjs

解决方案


问题是您将回调与 async/await 混合在一起,这意味着执行add()将在回调完成之前bcrypt.hash完成。这导致res.status在您的测试中尚未调用。

你可以通过等待bcrypt.hash调用来解决这个问题(它默认支持返回一个承诺):

// await hashing function instead of using callback
const hash = await bcrypt.hash(req.body.password, saltRounds);
const user = new UserModel();
user.email = req.body.email.toLowerCase();
user.password = hash;
// rest of the code ...

推荐阅读