PHPUnit报错无法存根/模拟resource类型,该如何解决?
问题本质
PHP的resource是基础数据类型,不是类、接口或trait,而PHPUnit的createStub()/createMock()方法只能针对类、接口这类可实例化的结构创建模拟对象,所以直接传入resource::class或者gettype()返回的字符串会报错——因为根本不存在名为resource的类。
解决方案
针对你的测试需求(模拟资源的行读取行为),推荐以下几种可行方案:
方案1:封装资源操作到类中(最推荐)
把对文件资源的操作逻辑封装成独立类,通过依赖注入的方式传给被测方法,这样就能轻松mock这个类的方法。
示例代码:
// 封装文件读取逻辑的类 class FileLineReader { private $resource; public function __construct($resource) { $this->resource = $resource; } public function readLine() { return fgets($this->resource); } } // 改造被测方法,依赖注入FileLineReader function my_method_under_test(FileLineReader $reader) { $line = $reader->readLine(); if (str_starts_with($line, '#version:')) { return trim(substr($line, 9)); } return ''; } // 测试代码 class TestDataAccess extends TestCase { public function testGetDataVersion() : void { $data_format = "1.0"; // mock封装类 $readerMock = $this->createStub(FileLineReader::class); $readerMock->method('readLine') ->willReturnOnConsecutiveCalls("#version:{$data_format}", false); $result = my_method_under_test($readerMock); self::assertSame($data_format, $result); } }
方案2:模拟全局文件操作函数
如果被测方法直接调用fgets()这类全局函数,可以在测试命名空间下重写该函数,让它返回预期值(PHP允许同命名空间下的函数覆盖全局函数)。
示例代码:
namespace TestNamespace; use PHPUnit\Framework\TestCase; // 在测试命名空间下重写fgets函数 function fgets($resource) { static $callCount = 0; $callCount++; return $callCount === 1 ? "#version:1.0\n" : false; } class TestDataAccess extends TestCase { public function testGetDataVersion() : void { // 随便传一个资源(甚至空值也可以,因为我们重写了fgets) $mockResource = fopen('php://memory', 'r'); $result = \my_method_under_test($mockResource); self::assertSame("1.0", $result); fclose($mockResource); } } // 全局命名空间下的被测方法 namespace { function my_method_under_test($resource) { $line = fgets($resource); if (str_starts_with($line, '#version:')) { return trim(substr($line, 9)); } return ''; } }
方案3:用SplFileObject替代真实资源
PHP内置的SplFileObject是面向对象的文件操作类,支持fgets()等方法,还可以直接创建内存文件模拟内容,无需mock就能满足测试需求。
示例代码:
class TestDataAccess extends TestCase { public function testGetDataVersion() : void { $data_format = "1.0"; // 创建内存文件并写入测试内容 $file = new \SplFileObject('php://memory', 'w+'); $file->fwrite("#version:{$data_format}\n"); $file->rewind(); // 回到文件开头 $result = my_method_under_test($file); self::assertSame($data_format, $result); } } // 被测方法可以直接接收SplFileObject function my_method_under_test($resource) { $line = $resource->fgets(); if (str_starts_with($line, '#version:')) { return trim(substr($line, 9)); } return ''; }
内容的提问来源于stack exchange,提问作者Without Haste
相关产品推荐
相关产品推荐

