如何为连接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'}] }); }); });
额外注意事项
- 安装依赖:确保在
functions目录下安装Jest:npm install jest --save-dev,然后在package.json里添加测试脚本:"test": "jest",之后就能用npm test运行测试了。 - Context参数:如果你的云函数用到了
context(比如验证用户身份),测试时要传入模拟的context对象,比如{ auth: { uid: 'test_user_123' } }。 - 环境变量:如果用Mock方案,不需要设置真实的DB环境变量;如果用真实测试库,一定要确保测试环境的变量不会指向生产数据库!
备注:内容来源于stack exchange,提问作者Savo Simeunovic
相关产品推荐
相关产品推荐

