如何为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); }
问题分析与修复方案
问题点
- 未注入模拟数据库连接:创建了
dbMock但没有赋值给LoginFormClass实例的$this->c属性,函数仍调用真实数据库连接,触发连接失败。 - Mock不完整:仅Mock了
Connection类,但未覆盖prepare、stmt、result等后续方法调用链,执行时会返回真实对象或null,引发报错。 - 断言逻辑错误:
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
相关产品推荐
相关产品推荐

