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

如何为mysqli预处理语句编写PHP Unit测试?

问题:为ValidateToken函数编写PHPUnit测试失败,提示无法连接数据库

需要测试的ValidateToken函数代码:

function ValidateToken(string $key, string $token): array {
    if (!empty($key) || !empty($token)) {
        $email = "%".htmlspecialchars($key)."%";
        $token = htmlspecialchars($token);
        $stmt = $this->c->prepare("SELECT expDate FROM `authuser` WHERE `reset_link_token`= ? AND `email1` LIKE ?");
        $stmt->bind_param('ss',$token,$email);
        $stmt->execute();
        $result = $stmt->get_result();
        $row = $result->fetch_array(MYSQLI_ASSOC);
        $curDate = date("Y-m-d H:i:s");
        if ($result->num_rows > 0) {
            if($row['expDate'] >= $curDate){
                return array("response"=>1,"email"=>$key,"token"=>$token);
            } else {
                return array("response"=>2);
            }
        } else {
            return array("response"=>0);
        }
    } else {
        return array("response"=>0);
    }
}

当前编写的测试代码(运行报错无法连接数据库):

public function testExpiredTokenSuccess() {
    $successToken = new stdClass();
    $successToken->expDate = '2099-12-31 23:59:59'; // y2.1k bug
    $this->mockLF = array();

    $this->mockLF[0] = $successToken ;
    $this->dbMock = $this->getMockBuilder(Connection::class)    
        ->disableOriginalConstructor()
        ->getMock();

    $curDate = date("Y-m-d H:i:s");

    $this->authuserloginclass = new LoginFormClass();
    $returnVal = $this->authuserloginclass->ValidateToken('key','token');
    $this->assertEquals("1", $returnVal);
}
问题分析与修复方案

问题点

  1. 未注入模拟数据库连接:创建了dbMock但没有赋值给LoginFormClass实例的$this->c属性,函数仍调用真实数据库连接,触发连接失败。
  2. Mock不完整:仅Mock了Connection类,但未覆盖prepare、stmt、result等后续方法调用链,执行时会返回真实对象或null,引发报错。
  3. 断言逻辑错误:ValidateToken返回数组,直接用字符串"1"和数组做相等断言,逻辑完全错误。

修复步骤与代码

1. 确保数据库连接可注入

调整LoginFormClass构造函数以支持连接注入(推荐方式):

class LoginFormClass {
    protected $c;

    public function __construct($dbConnection) {
        $this->c = $dbConnection;
    }

    // 其他方法包括ValidateToken
}

2. 完整Mock调用链并注入

修复后的测试代码:

public function testValidTokenSuccess() {
    // Mock Result对象
    $resultMock = $this->getMockBuilder(mysqli_result::class)
        ->disableOriginalConstructor()
        ->getMock();
    
    $resultMock->method('num_rows')
        ->willReturn(1);
    
    $resultMock->method('fetch_array')
        ->with(MYSQLI_ASSOC)
        ->willReturn(['expDate' => '2099-12-31 23:59:59']);

    // Mock Statement对象
    $stmtMock = $this->getMockBuilder(mysqli_stmt::class)
        ->disableOriginalConstructor()
        ->getMock();
    
    $stmtMock->method('bind_param')
        ->willReturn(true);
    
    $stmtMock->method('execute')
        ->willReturn(true);
    
    $stmtMock->method('get_result')
        ->willReturn($resultMock);

    // Mock Connection对象(如果是自定义Connection类,替换为对应类名)
    $dbMock = $this->getMockBuilder(mysqli::class)
        ->disableOriginalConstructor()
        ->getMock();
    
    $dbMock->method('prepare')
        ->with("SELECT expDate FROM `authuser` WHERE `reset_link_token`= ? AND `email1` LIKE ?")
        ->willReturn($stmtMock);

    // 注入Mock连接到LoginFormClass实例
    $authUserLoginClass = new LoginFormClass($dbMock);

    // 执行测试并断言
    $returnVal = $authUserLoginClass->ValidateToken('test@example.com', 'valid_token');
    
    $this->assertEquals(1, $returnVal['response']);
    $this->assertEquals('test@example.com', $returnVal['email']);
    $this->assertEquals('valid_token', $returnVal['token']);
}

补充说明

  • 若使用自定义Connection类,需对应Mock该类的prepare方法,确保返回Mock的stmt对象。
  • 原测试方法名testExpiredTokenSuccess与测试场景不符,已改为testValidTokenSuccess。

内容的提问来源于stack exchange,提问作者Dave

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 18:40:55