Nest.js 中的模拟 bcrypt 模块

6 javascript unit-testing node.js jestjs nestjs

我正在尝试模拟 bcrypt 哈希方法实现,但出现以下错误:

Error: thrown: "Exceeded timeout of 5000 ms for a test.
Use jest.setTimeout(newTimeout) to increase the timeout value, if this is a long-running test."
Run Code Online (Sandbox Code Playgroud)

我尝试将超时增加到 30000。此外,我还尝试模拟整个 bcrypt 模块,例如 jest.mock('bcrypt')。我是测试新手,可能存在一些逻辑错误或不良做法。如果您指出它们,我将不胜感激。

我的代码:

import { UserService } from '../user.service';
import { Test, TestingModule } from '@nestjs/testing';
import { Repository } from 'typeorm';
import { getRepositoryToken } from '@nestjs/typeorm';
import * as bcrypt from 'bcrypt';

import { UserEntity } from '../user.entity';
import { UserRepository } from '../user.repository';
import { CreateUserDto } from '../dto/create-user.dto';
import { userStub } from './stubs/user.stub';

describe('UserService', () => {
  let userService: UserService;
  let userRepository: Repository<UserEntity>;

  beforeAll(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        UserService,
        {
          provide: getRepositoryToken(UserRepository),
          useClass: Repository,
        },
      ],
    }).compile();

    userService = module.get<UserService>(UserService);
    userRepository = module.get<Repository<UserEntity>>(
      getRepositoryToken(UserRepository),
    );
  });

  it('should define UserService', () => {
    expect(userService).toBeDefined();
  });

  it('should define userRepository', () => {
    expect(userRepository).toBeDefined();
  });

  describe('createUser method', () => {
    it('has called with valid data', async () => {
      const createUserDto: CreateUserDto = {
        email: userStub().email,
        firstName: userStub().firstName,
        lastName: userStub().lastName,
        password: userStub().password,
      };
      const user: UserEntity = userStub();
      const spiedBcryptHashMethod = jest
        .spyOn(bcrypt, 'hash')
        .mockImplementation(() => Promise.resolve(''));
      const spiedRepositoryCreateMethod = jest
        .spyOn(userRepository, 'create')
        .mockReturnValue(user);
      const spiedRepositorySaveMethod = jest
        .spyOn(userRepository, 'save')
        .mockResolvedValue(user);

      const createUserResult = await userService.createUser(createUserDto);

      expect(spiedBcryptHashMethod).toHaveBeenCalled();
      expect(spiedRepositoryCreateMethod).toHaveBeenCalled();
      expect(spiedRepositorySaveMethod).toHaveBeenCalledWith(user);
      expect(createUserResult).toEqual(user);
    });
  });
});
Run Code Online (Sandbox Code Playgroud)

这里出现错误:

const spiedBcryptHashMethod = jest
        .spyOn(bcrypt, 'hash')
        .mockImplementation(() => Promise.resolve(''));
Run Code Online (Sandbox Code Playgroud)

我的用户服务代码:

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import * as bcrypt from 'bcrypt';

import { UserRepository } from './user.repository';
import { CreateUserDto } from './dto/create-user.dto';
import { UserEntity } from './user.entity';

@Injectable()
export class UserService {
  constructor(
    @InjectRepository(UserRepository) private userRepository: UserRepository,
  ) {}

  public async createUser(createUserDto: CreateUserDto): Promise<UserEntity> {
    return await this.userRepository.save(
      this.userRepository.create({
        ...createUserDto,
        password: await new Promise((resolve, reject) => {
          bcrypt.hash(createUserDto.password, 10, (err, encrypted) => {
            if (err) {
              reject(err);
            }

            resolve(encrypted);
          });
        }).then((onFilled: string) => onFilled),
      }),
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

Jay*_*iel 7

好的,关于您的服务代码有很多话要说......

您遇到的直接问题是因为您正在模拟 bcrypt 的hash方法来返回承诺,但使用该方法,因为它返回回调。如果您想继续使用回调与承诺方法混合,您需要执行类似的操作

jest.spyOn(bcrypt, 'hash').mockImplementation((pass, salt, cb) => cb(null, ''))
Run Code Online (Sandbox Code Playgroud)

这基本上与Promise.resolve('').

不过我不建议这样做。Bcrypt 内置了 Promise 支持,因此您不必用自己的 Promise 包装回调,而是可以这样做,await bcrypt.hash(pass, salt)您将得到散列密码,然后您Promise.resolve('')也将按照您的预期工作。我认为两者都没有必要then((onFullfilled: string) => onFullfilled),但如果您无论如何删除自定义承诺,这种情况就会消失。

一般来说,尝试坚持使用单一异步方法。所有回调(不再建议),所有承诺(更好),或所有异步/等待(现在几乎是标准)。