如何在PHPUnit测试中忽略私有pending()方法并访问pending_amount属性
代码文件
Receipt.php
<?php namespace TDD; class Receipt { private $user_id = 1; private $pending_amount; public function total(array $items = []){ $items[] = $this->pending(); return array_sum($items); } public function tax($amount,$tax){ return $amount * $tax; } private 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']; return $this->pending_amount = 45; } public function addTaxPending($tax){ return $this->pending_amount * $tax; } }
ReceiptTest.php
<?php namespace TDD\Test; require(dirname(dirname(__FILE__))).DIRECTORY_SEPARATOR.'vendor'.DIRECTORY_SEPARATOR.'autoload.php'; use PHPUnit\Framework\TestCase; use TDD\Receipt; class ReceiptTest extends TestCase{ public function setUp(): void { $this->Receipt = new Receipt(); } public function tearDown(): void{ unset($this->Receipt); } public function testTotal(){ $input = [0,2,5,8]; $output = $this->Receipt->total($input); $this->assertEquals(15,$output,"this is not valid"); } public function testTax(){ $inputAmount = 10.00; $inputTax = 0.10; $output = $this->Receipt->tax($inputAmount,$inputTax); $this->assertEquals(1.0,$output,"this tax expecting 1.0"); } }
问题描述
由于pending()方法涉及数据库查询,我希望在PHPUnit测试中跳过total()对该私有方法的内部调用,同时能访问Receipt类的$this->pending_amount属性,且pending()必须保持私有。请问该怎么实现?
解决方案
可以通过PHPUnit模拟私有方法+PHP反射访问私有属性的组合来实现,完全不需要修改原类的代码结构:
<?php namespace TDD\Test; require(dirname(dirname(__FILE__))).DIRECTORY_SEPARATOR.'vendor'.DIRECTORY_SEPARATOR.'autoload.php'; use PHPUnit\Framework\TestCase; use TDD\Receipt; use ReflectionClass; class ReceiptTest extends TestCase{ private $receipt; public function setUp(): void { // 创建Receipt的部分模拟对象,仅替换pending方法 $this->receipt = $this->getMockBuilder(Receipt::class) ->onlyMethods(['pending']) // 即使是私有方法也能指定模拟 ->getMock(); // 自定义pending方法的返回值,避免执行数据库逻辑 $testPendingAmount = 0; // 这里可设置任意测试用值 $this->receipt->method('pending') ->willReturn($testPendingAmount); // 用反射设置私有属性pending_amount,保证和模拟返回值一致 $reflection = new ReflectionClass($this->receipt); $pendingProp = $reflection->getProperty('pending_amount'); $pendingProp->setAccessible(true); $pendingProp->setValue($this->receipt, $testPendingAmount); } public function tearDown(): void{ unset($this->receipt); } public function testTotal(){ $input = [0,2,5,8]; $output = $this->receipt->total($input); $this->assertEquals(15,$output,"计算结果不符合预期"); } public function testTax(){ $inputAmount = 10.00; $inputTax = 0.10; $output = $this->receipt->tax($inputAmount,$inputTax); $this->assertEquals(1.0,$output,"税费计算不符合预期"); } // 额外测试addTaxPending方法,同样可以自定义pending_amount的值 public function testAddTaxPending(){ $taxRate = 0.1; $customPendingAmount = 100; $reflection = new ReflectionClass($this->receipt); $pendingProp = $reflection->getProperty('pending_amount'); $pendingProp->setAccessible(true); $pendingProp->setValue($this->receipt, $customPendingAmount); $output = $this->receipt->addTaxPending($taxRate); $this->assertEquals(10, $output, "待缴金额税费计算错误"); } }
核心逻辑说明
- 模拟私有方法:PHPUnit的
getMockBuilder支持模拟私有方法,只要在onlyMethods中指定方法名即可,完全不会修改原类的访问修饰符。 - 访问私有属性:利用PHP的
ReflectionClass可以临时将私有属性设置为可访问,直接修改其值,满足测试中对pending_amount的控制需求。 - 解耦数据库依赖:通过模拟
pending()的返回值,彻底跳过数据库查询逻辑,让测试只关注业务计算逻辑。
内容的提问来源于stack exchange,提问作者Siddhartha esunuri
相关产品推荐
相关产品推荐

