用Jest正确地模拟库/方法

时间:2019-03-14 02:53:03

标签: typescript unit-testing mocking jestjs

我正在尝试为一种方法编写单元测试,但在使事情顺利运行方面遇到一些麻烦。

我需要测试此类中唯一的public方法AuthenticateAdminService.authAdmin,该方法将调用private方法,该方法将调用jsonwebtoken等第三方库和bcrypt。我遇到的问题是,当我尝试将密码与bcrypt进行比较时,它在自动测试过程中返回为false,而在手动测试过程中返回了true

我应该如何可靠地模拟这些私有方法和库?我在SO上找到了几篇对我无济于事的文章,也许我只是听不懂答案。我发现this post on mocking libraries确实起到了作用,直到我意识到我正在使用的任何库都包装在一个私有方法内,这对我的测试没有好处,因为我没有直接调用模拟库。

我认为我应该创建AuthenticateAdminService类私有方法的模拟。测试实际的公共方法authAdmin,并以某种方式获取被调用的模拟私有方法,而不是实际版本。有帮助吗?

AuthenticateAdminService

class AuthenticateAdminService {

  public static async authAdmin(reqBody: RequestBody, AdminModel: AdminModel): Promise<object> {
    const { username, password } = reqBody
    const adminRow: AdminRow = await AdminModel.findOne({ where: { username } })
    let token: { token?: string } = {}
    if (adminRow !== null) {
      token = await AuthenticateAdminService.checkIfPasswordsMatch(password, adminRow)
    }
    return token
  }


  private static async checkIfPasswordsMatch(plainPassword: string, adminRow: AdminRow): Promise<object> {
    const isPasswordsMatch = await Promise.resolve(bcrypt.compare(plainPassword, adminRow.password))
    let token: object = {}
    if (isPasswordsMatch) {
      const admin: object = AuthenticateAdminService.removePasswordPropFromAdminRow(adminRow)
      token = { token: await AuthenticateAdminService.createToken(admin) }
    }
    return token
  }

  private static removePasswordPropFromAdminRow(adminRow: AdminRow): object {
    const { password, ...admin } = adminRow.dataValues
    return admin
  }

  private static async createToken(admin: object): Promise<string> {
    const token: string = await Promise.resolve(jwt.sign({ admin }, 'expressadminarea'))
    return token
  }

}

AuthenticateAdminService.test.js(失败)

import { AuthenticateAdminService } from '../../src/services/AuthenticateAdminService/AuthenticateAdminService'

type AdminModel = {
  findOne(where: object): AdminRow
}

type AdminRow = {
  password: string
  dataValues: { password: string }
}

test('authAdmin', async () => {
  const reqBody: { username: string, password: string } = { username: 'foo', password: 'foo' }
  const adminModel: AdminModel = { findOne: (_where) => <AdminRow>{ password: 'foo', dataValues: { password: 'foo' } } }
  const token: { token?: string } = await AuthenticateAdminService.authAdmin(reqBody, adminModel)

  expect(typeof token).toBe('object')
  expect(typeof token.token).toBe('string')  // token.token is undefined
  expect(token.token.length).toBeGreaterThan(0)
})

1 个答案:

答案 0 :(得分:1)

看起来您只需要用密码哈希而不是纯文本密码来模拟adminModel

test('authAdmin', async () => {
  const reqBody: { username: string, password: string } = { username: 'foo', password: 'foo' }
  const hash = await bcrypt.hash('foo', 10);  // create a hash
  const adminModel: AdminModel = { findOne: (_where) => <AdminRow>{ password: hash, dataValues: { password: hash } } }  // use the hash
  const token: { token?: string } = await AuthenticateAdminService.authAdmin(reqBody, adminModel)

  expect(typeof token).toBe('object')
  expect(typeof token.token).toBe('string')  // Success!
  expect(token.token.length).toBeGreaterThan(0)
})

此外,bcrypt.compare返回一个Promise,因此您可以简化该行:

const isPasswordsMatch = await bcrypt.compare(plainPassword, adminRow.password);