Laravel中如何用Storage::fake在PHPUnit测试中伪造storage_path()?
Laravel测试中伪造storage/history目录的解决方案
问题根源分析
1. Storage::fake(storage_path())报错原因
Storage::fake()的参数是文件系统磁盘的名称(对应config/filesystems.php里的磁盘配置项),不是本地绝对路径。你传入storage_path()后,系统会试图在测试临时目录(storage/framework/testing/disks/)下创建包含完整盘符的子目录(比如C:\xxx\yyy\storage),这违反了操作系统的路径规则,自然报错。
2. Storage::fake('storage')仍删除真实文件的原因
你的业务代码是直接通过storage_path()函数拼接路径,然后用原生PHP函数(比如unlink、glob)操作文件,这种方式会绕过Laravel的Storage facade。而Storage::fake()只对通过Storage facade调用的磁盘操作生效,所以无法拦截原生文件操作,导致真实文件被删除。
解决方案
方案一:改用Storage facade操作(推荐,符合Laravel最佳实践)
步骤1:配置自定义磁盘
在config/filesystems.php的disks数组中添加一个名为history的磁盘:
'disks' => [ // ... 其他已有磁盘配置 'history' => [ 'driver' => 'local', 'root' => storage_path('history'), ], ],
步骤2:修改业务代码
将所有直接操作storage/history的代码,替换为通过Storage facade调用history磁盘:
// 示例:写入文件 // 原代码:file_put_contents(storage_path('history/daily_20240520.json'), json_encode($data)); Storage::disk('history')->put('daily_20240520.json', json_encode($data)); // 示例:删除单个文件 // 原代码:unlink(storage_path('history/daily_20240520.json')); Storage::disk('history')->delete('daily_20240520.json'); // 示例:删除目录下所有文件 // 原代码:array_map('unlink', glob(storage_path('history/*.json'))); Storage::disk('history')->delete(Storage::disk('history')->files());
步骤3:测试中伪造磁盘
在PHPUnit测试方法中,直接伪造history磁盘即可:
use Illuminate\Support\Facades\Storage; public function test_delete_history_files() { // 伪造history磁盘,所有操作都会指向临时目录 Storage::fake('history'); // 先写入测试文件 Storage::disk('history')->put('test1.json', 'test data'); Storage::disk('history')->put('test2.json', 'test data'); // 调用你的删除函数 $this->callDeleteHistoryFilesFunction(); // 断言文件已被删除 Storage::disk('history')->assertMissing('test1.json'); Storage::disk('history')->assertMissing('test2.json'); }
方案二:模拟storage_path()函数(适合无法修改业务代码的场景)
如果不能修改原有业务代码,可以通过模拟storage_path()辅助函数,将storage/history的路径指向测试临时目录:
use Illuminate\Support\Facades\File; use Mockery; public function setUp(): void { parent::setUp(); // 模拟storage_path函数,替换history路径为测试临时目录 Mockery::mock('function:storage_path', function ($mock) { $mock->shouldReceive('storage_path')->withAnyArgs()->andReturnUsing(function ($path) { if ($path === 'history' || str_starts_with($path, 'history/')) { $testPath = base_path('tests/Temp/history'); // 确保测试目录存在 File::ensureDirectoryExists($testPath); return $testPath . '/' . ltrim($path, 'history/'); } // 其他storage_path调用保持原路径 return storage_path($path); }); }); } public function tearDown(): void { // 测试结束后清理临时目录 File::deleteDirectory(base_path('tests/Temp/history')); parent::tearDown(); }
内容的提问来源于stack exchange,提问作者Hoyt Jolly
相关产品推荐
相关产品推荐

