You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为连接MySQL数据库的Firebase HTTP云函数编写单元测试

如何为连接MySQL数据库的Firebase HTTP云函数编写单元测试

我完全懂你的困惑——Firebase官方的测试文档几乎全是针对自家Firestore或实时数据库的例子,碰到MySQL这种外部数据库的场景,确实没太多现成参考。咱们一步步拆解问题,搞定这个单元测试。

首先得明确:单元测试的核心是隔离外部依赖,要么用Mock模拟数据库交互,要么搭建一个专门的测试用MySQL数据库(更偏向集成测试)。先从最常用的Mock方案说起,这也是纯单元测试的最佳实践。


方案一:Mock MySQL连接(纯单元测试)

你的云函数依赖mysql2的连接池来执行查询,所以我们可以用Jest(Firebase Functions Test默认配合Jest)来Mock整个mysql2模块,让它返回假的连接池和查询结果,完全不用真的连数据库。

步骤1:调整测试文件的Mock逻辑

修改你的index.test.js,替换成下面的代码:

const functions = require("firebase-functions-test")();
const mysql = require('../mysql.js');
const mysql2 = require('mysql2');

// Mock mysql2的createPool方法,返回假的连接池
jest.mock('mysql2', () => {
  // 模拟promisePool的query方法
  const mockPromisePool = {
    query: jest.fn()
  };
  // 模拟普通连接池的promise()方法,返回上面的mockPromisePool
  const mockPool = {
    promise: jest.fn(() => mockPromisePool)
  };
  return {
    createPool: jest.fn(() => mockPool)
  };
});

describe("Testing getRoots Cloud Function", () => {
  const wrappedGetRoots = functions.wrap(mysql.getRoots);

  // 每个测试用例前重置Mock的调用记录和返回值
  beforeEach(() => {
    mysql2.createPool().promise().query.mockReset();
  });

  // 测试成功场景:数据库查询正常返回结果
  it('should return roots data when queries succeed', async () => {
    // 预设两个SQL查询的返回值,对应你的CALL和SELECT语句
    const mockCallResult = []; // CALL语句的结果可以是空数组(不影响后续逻辑)
    const mockSelectResult = [{ '@temp': JSON.stringify([{id:1, name:'root1'}, {id:2, name:'root2'}]) }];
    
    // 让第一个query返回CALL的结果,第二个返回SELECT的结果
    mysql2.createPool().promise().query
      .mockResolvedValueOnce([mockCallResult, null])
      .mockResolvedValueOnce([mockSelectResult, null]);

    // 调用被包裹的云函数,空data和context(如果你的函数不需要验证身份,这样就行)
    const response = await wrappedGetRoots({}, {});
    
    // 断言返回值符合预期(和你的函数返回结构一致:{data: 解析后的JSON})
    expect(response).toEqual({
      data: [{id:1, name:'root1'}, {id:2, name:'root2'}]
    });
    
    // 断言query被调用了两次,且参数是正确的SQL语句
    expect(mysql2.createPool().promise().query).toHaveBeenCalledTimes(2);
    expect(mysql2.createPool().promise().query).toHaveBeenNthCalledWith(1, "CALL get_roots(@temp);");
    expect(mysql2.createPool().promise().query).toHaveBeenNthCalledWith(2, "SELECT @temp;");
  });

  // 测试失败场景:数据库查询抛出错误
  it('should return null when database query fails', async () => {
    // 预设第一个query抛出错误
    const mockError = new Error('Database connection timeout');
    mysql2.createPool().promise().query.mockRejectedValueOnce(mockError);

    const response = await wrappedGetRoots({}, {});
    
    // 断言错误被捕获,函数返回null(和你的代码逻辑一致)
    expect(response).toBeNull();
    // 断言query只被调用了一次(因为第一次就失败了)
    expect(mysql2.createPool().promise().query).toHaveBeenCalledTimes(1);
  });
});

为什么你的原测试不工作?

你原测试里写了expect(response.status).toBe('OK'),但你的云函数成功时返回的是{data: JSON.parse(result)},失败时返回null,根本没有status字段——这是测试失败的直接原因,得根据函数的实际返回结构来写断言。


方案二:使用真实测试MySQL数据库(集成测试)

如果想验证数据库交互的真实逻辑(比如存储过程是否正确执行),可以搭建一个专门的测试用MySQL数据库,这种属于集成测试,更贴近生产环境。

步骤1:准备测试数据库

可以用Docker快速启动一个临时MySQL容器,或者用专门的测试数据库实例,确保它和生产环境的表结构、存储过程完全一致。

步骤2:修改测试文件,配置测试环境变量

const functions = require("firebase-functions-test")();
const mysql = require('../mysql.js');
const mysql2 = require('mysql2/promise');

// 设置测试数据库的环境变量(替换成你的测试库信息)
process.env.DB_HOST = 'localhost';
process.env.DB_PORT = '3306';
process.env.DB_USERNAME = 'test_user';
process.env.DB_PASSWORD = 'test_pass';
process.env.DB_DATABASE = 'test_db';

describe("Testing getRoots with real MySQL Database", () => {
  const wrappedGetRoots = functions.wrap(mysql.getRoots);
  let testDbConnection;

  // 测试前初始化数据库:创建存储过程、插入测试数据
  beforeAll(async () => {
    testDbConnection = await mysql2.createConnection({
      host: process.env.DB_HOST,
      port: process.env.DB_PORT,
      user: process.env.DB_USERNAME,
      password: process.env.DB_PASSWORD,
      database: process.env.DB_DATABASE,
    });
    // 创建测试用的get_roots存储过程(和生产环境一致)
    await testDbConnection.execute(`
      CREATE PROCEDURE get_roots(OUT temp JSON) 
      BEGIN 
        SET temp = '[{"id":1,"name":"test_root"}]'; 
      END
    `);
  });

  // 测试后清理数据:删除存储过程、关闭连接
  afterAll(async () => {
    await testDbConnection.execute(`DROP PROCEDURE get_roots`);
    await testDbConnection.end();
  });

  it('should return correct data from real test database', async () => {
    const response = await wrappedGetRoots({}, {});
    expect(response).toEqual({
      data: [{id:1, name:'test_root'}]
    });
  });
});

额外注意事项

  1. 安装依赖:确保在functions目录下安装Jest:npm install jest --save-dev,然后在package.json里添加测试脚本:"test": "jest",之后就能用npm test运行测试了。
  2. Context参数:如果你的云函数用到了context(比如验证用户身份),测试时要传入模拟的context对象,比如{ auth: { uid: 'test_user_123' } }。
  3. 环境变量:如果用Mock方案,不需要设置真实的DB环境变量;如果用真实测试库,一定要确保测试环境的变量不会指向生产数据库!

备注:内容来源于stack exchange,提问作者Savo Simeunovic

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.22 10:23:01