如何用PHPUnit测试含私有方法依赖的PHP类?
如何测试依赖私有方法的PHP公共方法(PHPUnit)
核心原则:测试公共契约,而非私有实现
不需要直接测试私有方法——它们是类的内部实现细节,属于可随时调整的部分。你的核心目标是验证requestToken这个公共方法的行为是否符合预期:只要传入特定的clientId能得到正确的输出,就说明内部逻辑(包括所有私有方法的调用)是正常的。
分场景测试requestToken
针对requestToken的三个核心分支,编写对应的测试用例,同时用PHPUnit的Mock功能隔离外部依赖(数据库、第三方API),避免真实调用影响测试的稳定性和速度:
1. 场景:数据库中无对应Token
当传入的clientId在数据库中找不到Token时,requestToken应返回null。
2. 场景:数据库中有未过期的Token
此时requestToken应直接返回该Token,无需触发刷新逻辑。
3. 场景:数据库中有已过期的Token
此时requestToken应调用第三方API获取新Token并返回。
测试代码示例
下面是适配你现有代码的PHPUnit测试用例(假设基于Laravel框架,使用Laravel自带的Mock功能):
use PHPUnit\Framework\TestCase; use App\Model\OauthToken; use App\A; use stdClass; class ATest extends TestCase { public function test_request_token_returns_null_when_no_token_in_db() { // Mock OauthToken模型,模拟查询无结果 $this->mock(OauthToken::class, function ($mock) { $mock->shouldReceive('where')->with('clientId', 123)->andReturnSelf(); $mock->shouldReceive('first')->andReturn(null); }); $a = new A(); $result = $a->requestToken(123); $this->assertNull($result); } public function test_request_token_returns_existing_token_when_not_expired() { // 创建未过期的Token对象 $validToken = new stdClass(); $validToken->refreshToken = 'old_refresh_token'; // Mock数据库查询返回有效Token $this->mock(OauthToken::class, function ($mock) use ($validToken) { $mock->shouldReceive('where')->with('clientId', 456)->andReturnSelf(); $mock->shouldReceive('first')->andReturn($validToken); }); // 部分Mock类A,模拟token未过期的判断结果 $aMock = $this->getMockBuilder(A::class) ->onlyMethods(['checkIfTokenExpired']) ->getMock(); $aMock->method('checkIfTokenExpired')->with($validToken)->willReturn(false); $result = $aMock->requestToken(456); $this->assertEquals($validToken, $result); } public function test_request_token_returns_new_token_when_existing_is_expired() { // 创建已过期的Token对象 $expiredToken = new stdClass(); $expiredToken->refreshToken = 'expired_refresh_token'; $newToken = 'new_access_token'; // Mock数据库查询返回过期Token $this->mock(OauthToken::class, function ($mock) use ($expiredToken) { $mock->shouldReceive('where')->with('clientId', 789)->andReturnSelf(); $mock->shouldReceive('first')->andReturn($expiredToken); }); // 部分Mock类A,模拟token过期判断和新Token获取 $aMock = $this->getMockBuilder(A::class) ->onlyMethods(['checkIfTokenExpired', 'getNewToken']) ->getMock(); $aMock->method('checkIfTokenExpired')->with($expiredToken)->willReturn(true); $aMock->method('getNewToken')->with('expired_refresh_token')->willReturn($newToken); $result = $aMock->requestToken(789); $this->assertEquals($newToken, $result); } }
优化建议(提升可测试性)
如果觉得Mock私有方法不够优雅,推荐重构代码,通过依赖注入降低耦合:
- 把数据库查询逻辑封装成
OauthTokenRepository类,注入到A中,测试时直接Mock这个仓库的方法。 - 把第三方API调用封装成
OauthRefreshService类,注入到A中,getNewToken调用该服务的方法,测试时Mock服务的返回。
重构后的类示例:
use App\Repository\OauthTokenRepository; use App\Service\OauthRefreshService; class A { public function __construct( private OauthTokenRepository $tokenRepo, private OauthRefreshService $refreshService ) {} public function requestToken(int $clientId): object|array|null { $token = $this->tokenRepo->getByClientId($clientId); if($token && $this->checkIfTokenExpired($token)) { $token = $this->refreshService->getNewToken($token->refreshToken); } return $token; } private function checkIfTokenExpired($token): bool { // 过期判断逻辑 } }
这种方式下,测试只需Mock外部依赖类,无需处理私有方法,代码的可维护性和可测试性都会大幅提升。
内容的提问来源于stack exchange,提问作者Alex
相关产品推荐
相关产品推荐

