You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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私有方法不够优雅,推荐重构代码,通过依赖注入降低耦合:

  1. 把数据库查询逻辑封装成OauthTokenRepository类,注入到A中,测试时直接Mock这个仓库的方法。
  2. 把第三方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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 23:47:46