如何在PHPUnit测试中跳过内部私有调用并访问私有属性?
问题解决方案
错误原因
你尝试mock私有方法pending()导致报错,核心原因是PHPUnit的MockBuilder仅能配置公共方法,私有方法属于类内部实现细节,无法直接被mock或配置。
解决方案
方案1:修改原代码(推荐,符合可测试性设计)
将Receipt类中的pending()方法从private改为protected(私有方法是类内部实现细节,若公共方法依赖其逻辑,改为protected更利于测试和扩展):
// Receipt.php namespace TDD; class Receipt { // ... 其他代码不变 ... protected function pending() { $sql = 'select pending_amount from Pending_transtions where user_id =' . $this->user_id . ' limit 1;'; //$pending_amt = $this->mainDb->get_sql_row($sql); //$this->pending = $pending_amt['pending_amount']; $this->pending_amount = 55; } // ... 其他代码不变 ... }
然后编写测试方法:
// ReceiptTest.php public function testTotalWithoutPending() { // 创建部分Mock,仅mock protected的pending()方法 $receipt = $this->getMockBuilder(Receipt::class) ->setConstructorArgs(['s', 'p']) ->onlyMethods(['pending']) // PHPUnit 9+使用onlyMethods,旧版本用setMethods ->getMock(); // 让mock的pending()方法不执行任何逻辑 $receipt->method('pending') ->willReturn(null); // 访问并设置私有属性pending_amount的测试值 $pendingProp = new \ReflectionProperty(Receipt::class, 'pending_amount'); $pendingProp->setAccessible(true); $pendingProp->setValue($receipt, 45); $input = [0, 2, 5, 8]; $expectedTotal = array_sum($input) + 45; // 计算预期结果:15+45=60 $output = $receipt->total($input); $this->assertEquals($expectedTotal, $output); }
方案2:不修改原代码(纯Reflection实现)
若无法修改原代码,可通过匿名子类重写total()方法,跳过pending()调用,同时用Reflection访问私有属性:
// ReceiptTest.php public function testTotalWithoutPending() { $input = [0, 2, 5, 8]; $expectedPendingAmount = 45; $expectedTotal = array_sum($input) + $expectedPendingAmount; // 创建匿名子类,重写total()方法跳过pending()调用 $receipt = new class extends Receipt { public function total(array $items = []) { // 用Reflection访问父类私有属性pending_amount $prop = new \ReflectionProperty(parent::class, 'pending_amount'); $prop->setAccessible(true); $items[] = $prop->getValue($this); return array_sum($items); } }; // 设置私有属性pending_amount的测试值 $pendingProp = new \ReflectionProperty(Receipt::class, 'pending_amount'); $pendingProp->setAccessible(true); $pendingProp->setValue($receipt, $expectedPendingAmount); $output = $receipt->total($input); $this->assertEquals($expectedTotal, $output); }
关于访问私有属性pending_amount
无论哪种方案,都可通过ReflectionProperty实现对私有属性的读写:
// 获取私有属性并设置为可访问 $prop = new \ReflectionProperty(Receipt::class, 'pending_amount'); $prop->setAccessible(true); // 设置属性值 $prop->setValue($receipt, 45); // 获取属性值 $currentValue = $prop->getValue($receipt);
内容的提问来源于stack exchange,提问作者Siddhartha
相关产品推荐
相关产品推荐

