网站搜索

单元测试 - 如何在 TypeScript 中使用 Jest 模拟外部依赖


我正在绝望地尝试在 TypeScript 中模拟 firebase admin 依赖以进行单元测试。

我的实际功能如下

import {auth} from 'firebase-admin';

async validateUser(payload: string): Promise<User> {
  try {
    const resp = await auth().verifyIdToken(payload, true);

    return await this.userService.findByUsername(resp.email);
  } catch (e) {
    return null;
 }
}

我正在尝试通过模拟 admin 函数来测试 admin.auth().verifyIdToken(payload, true)。我尝试了如下测试代码

import {auth} from 'firebase-admin';

jest.mock('firebase-admin');

const mockedAuth = auth as jest.Mock;

mockedAuth.mockReturnValue({
  verifyIdToken: jest.fn().mockResolvedValue(true),
});

但是我收到一个错误,显示 TypeError: Cannot read property 'mockReturnValue' of undefined

如果我理解正确的话,我是通过调用 jest.mock('firebase-admin') 来模拟整个 firebase-admin 包。所以我本来希望能够调用 mockReturnValue,并再次模拟 auth 函数中的 verifyIdToken 函数。然而,我甚至无法调用 mockReturnValue,因为奇怪的是它似乎并不存在。

我需要如何编写代码,以便我可以模拟 auth().verifyIdToken() 的已解析值?

答案#1

使用 Jest mock 工厂来模拟 firebase 模块。

jest.mock('firebase-admin', () => {
  return {
    auth: jest.fn(),
  }
});

规格文件示例:

import { auth } from 'firebase-admin';
import Test from './index';

jest.mock('firebase-admin', () => {
  return {
    auth: jest.fn(),
  }
});

describe("Test", () => {
  const email = '[email ';
  const user = { email, username: 'mocked-username' };

  let mockedAuth: jest.Mock;
  let mockVerifyIdToken: jest.Mock;
  let mockUserService: { findByUsername: jest.Mock }; // mock userService
  let testInstance: Test;

  beforeEach(() => {
    mockedAuth = auth as jest.Mock;
    mockVerifyIdToken = jest.fn()
    mockedAuth.mockReturnValue({
      verifyIdToken: mockVerifyIdToken,
    });

    mockUserService = {
      findByUsername: jest.fn(),
    };

    testInstance = new Test(mockUserService);
  });

  test("should return user info when pass correct payload", async () => {
    const payload = 'mocked-payload';

    mockVerifyIdToken.mockResolvedValue({ email });
    mockUserService.findByUsername.mockResolvedValue(user);

    const result = await testInstance.validateUser(payload);

    expect(result).toBe(user);
    expect(mockVerifyIdToken).toHaveBeenCalledWith(payload, true);
    expect(mockUserService.findByUsername).toHaveBeenCalledWith(email);
  });
})

答案#2

这个案例的问题是,auth 和 firebase-admin 中类似的导出函数是通过 Object.defineProperty 定义为 getter 的。不幸的是,当 jest.mock 评估模块时,它看不到这些,因此不会为它们添加模拟。

你需要在提供给 jest.mock 的工厂中模拟 auth(以及你需要的任何同级函数)。方法如下,将你的第二个代码片段中的 jest.mock 行替换成这个:

jest.mock('firebase-admin', () =>
{
  const module = jest.createMockFromModule<any>('firebase-admin').default;
  module.auth = jest.fn();
  return module;
});

你也可以选择在那个工厂中模拟 auth 的返回值,以进一步简化你的代码。