如何为PDF生成接口搭建PHPUnit功能测试且不修改原有逻辑
PDF下载接口的功能测试方案
问题场景
- 应用有两个PDF生成入口:
/pdf/report和/pdf/details - 对应调用
PDFController的report()和details()方法收集生成PDF所需的数据 - 两个控制器方法最终都会调用
PDFService类的outputPDF($pdf_data)方法,该方法内部通过$pdf->getMpdf()->Output($pdf_data['filename'], 'D')触发浏览器下载
核心痛点
直接修改Output()的输出参数为'S'会改变原有下载逻辑;为测试场景单独给outputPDF()加参数又显得冗余,找不到合理的测试方案
方案1:依赖注入替换Mpdf实例(推荐)
通过依赖注入容器,在测试环境下替换真实的Mpdf实例为Mock对象,拦截Output()调用:
- 测试时创建Mock版Mpdf类,重写
Output()方法,不触发真实下载,而是记录调用参数或返回模拟内容 - 将Mock实例绑定到容器中,让
PDFService使用这个Mock对象而非真实实例 - 发起接口请求后,验证Mock的
Output()是否按预期被调用(检查文件名、输出模式'D'),同时断言响应头是否符合下载要求(比如Content-Disposition包含attachment和对应文件名)
示例代码片段:
// 测试用例中 $mockMpdf = $this->createMock(Mpdf::class); // 验证Output方法是否以正确参数被调用一次 $mockMpdf->expects($this->once()) ->method('Output') ->with($this->equalTo('report.pdf'), $this->equalTo('D')); // 替换容器中的Mpdf实例 $this->app->instance(Mpdf::class, $mockMpdf); // 发起接口请求 $response = $this->get('/pdf/report'); // 验证响应头是否符合下载要求 $this->assertEquals('attachment; filename="report.pdf"', $response->headers->get('Content-Disposition'));
方案2:环境变量控制输出模式
在PDFService::outputPDF()中增加环境判断,测试环境自动切换输出模式为'S',不改变对外方法签名:
// PDFService.php public function outputPDF($pdf_data) { // 测试环境下返回PDF内容,生产环境触发下载 $outputMode = env('APP_ENV') === 'testing' ? 'S' : 'D'; return $this->pdf->getMpdf()->Output($pdf_data['filename'], $outputMode); }
- 测试时设置
APP_ENV=testing,方法会返回PDF内容而非触发下载 - 发起请求后,可以直接断言返回内容是否为有效PDF(比如检查开头是否是
%PDF-),同时验证响应头的正确性
方案3:拦截输出流捕获PDF内容
利用PHP输出控制函数在测试时捕获输出内容,无需修改业务代码:
- 发起请求前开启输出缓冲
- 请求结束后获取缓冲内容,同时检查响应头是否符合下载要求
- 注意:部分框架有自己的输出控制逻辑,可能需要适配框架的测试API
示例代码片段:
// 测试用例中 ob_start(); $response = $this->get('/pdf/report'); $pdfContent = ob_get_clean(); // 验证返回内容是合法PDF $this->assertStringStartsWith('%PDF-', $pdfContent); // 验证响应头 $this->assertEquals('attachment; filename="report.pdf"', $response->headers->get('Content-Disposition'));
内容的提问来源于stack exchange,提问作者Cynical
相关产品推荐
相关产品推荐

