如何在Jest中优雅复用Puppeteer模拟代码?
可复用的Puppeteer Jest模拟方案
针对你遇到的模拟代码冗长、复用性差且无法监听方法调用的问题,这里有两个适配Node.js v18.13.0和Jest v25.3.0环境的实用优化方案:
方案一:增强__mocks__全局模拟,支持调用监听
直接在项目根目录的__mocks__文件夹下创建puppeteer.js,用jest.fn()实现链式API,既全局复用,又能跟踪所有调用情况:
// __mocks__/puppeteer.js // 模拟Page对象的核心方法 const mockPage = { screenshot: jest.fn().mockResolvedValue(Buffer.from('mock-screenshot-data')), goto: jest.fn().mockResolvedValue(true), // 按需添加其他需要模拟的Page方法,如setViewport、evaluate等 }; // 模拟Browser对象 const mockBrowser = { newPage: jest.fn().mockResolvedValue(mockPage), close: jest.fn().mockResolvedValue(true), }; // 模拟Puppeteer顶层API const mockPuppeteer = { launch: jest.fn().mockResolvedValue(mockBrowser), // 按需添加其他顶层方法,如connect、defaultArgs等 }; module.exports = mockPuppeteer;
使用方式
在测试文件开头启用全局模拟,即可直接断言调用:
jest.mock('puppeteer'); const puppeteer = require('puppeteer'); const takeScreenshot = require('./your-screenshot-function'); test('截图函数正确调用Puppeteer API', async () => { await takeScreenshot('https://example.com', './output.png'); // 断言launch方法被调用 expect(puppeteer.launch).toHaveBeenCalled(); // 断言浏览器实例调用了newPage expect(puppeteer.launch().newPage).toHaveBeenCalled(); // 断言截图方法使用了正确参数 expect(mockPage.screenshot).toHaveBeenCalledWith({ path: './output.png' }); });
如果需要在单个测试中覆盖默认模拟行为,直接修改对应方法的模拟即可:
test('截图失败时抛出错误', async () => { mockPage.screenshot.mockRejectedValue(new Error('截图超时')); await expect(takeScreenshot('https://example.com')).rejects.toThrow('截图超时'); });
方案二:工厂函数生成定制化模拟实例
如果不同测试场景需要不同的模拟配置(比如部分测试不需要模拟goto,部分需要自定义返回值),可以写一个工厂函数来生成灵活的模拟对象:
// test/utils/mock-puppeteer.js function createMockPuppeteer(customOverrides = {}) { // 合并默认配置与自定义覆盖项 const mockPage = { screenshot: jest.fn().mockResolvedValue(Buffer.from('default-screenshot')), goto: jest.fn().mockResolvedValue(true), ...customOverrides.page, }; const mockBrowser = { newPage: jest.fn().mockResolvedValue(mockPage), close: jest.fn().mockResolvedValue(true), ...customOverrides.browser, }; const mockPuppeteer = { launch: jest.fn().mockResolvedValue(mockBrowser), ...customOverrides.puppeteer, }; // 返回所有模拟对象,方便测试中直接断言 return { puppeteer: mockPuppeteer, browser: mockBrowser, page: mockPage }; } module.exports = createMockPuppeteer;
使用方式
在测试中导入工厂函数,生成符合当前场景的模拟:
const createMockPuppeteer = require('./test/utils/mock-puppeteer'); const takeScreenshot = require('./your-screenshot-function'); test('自定义截图参数时的调用逻辑', async () => { // 生成带自定义覆盖的模拟实例 const { puppeteer, page } = createMockPuppeteer({ page: { screenshot: jest.fn().mockResolvedValue(Buffer.from('custom-size-screenshot')), }, }); // 模拟puppeteer模块返回我们的定制实例 jest.mock('puppeteer', () => puppeteer); const puppeteerMock = require('puppeteer'); await takeScreenshot('https://example.com', './fullpage.png', { fullPage: true }); expect(page.screenshot).toHaveBeenCalledWith({ path: './fullpage.png', fullPage: true }); });
关键注意事项
- 所有异步方法(如
launch、screenshot)必须用mockResolvedValue/mockRejectedValue返回Promise,适配Puppeteer的异步API规范。 - Jest 25.3.0完全支持
jest.fn()的链式断言和模拟覆盖,无需额外配置。 - 如果项目使用ES模块(
import/export),将module.exports改为export default即可,Jest会自动识别。
内容的提问来源于stack exchange,提问作者ytnkgt
相关产品推荐
相关产品推荐

