应Mock封装类(如UserRepository)所依赖的PDO实例,而非PDO本身;需逐层Mock PDOStatement 的 execute() 和 fetch() 方法并匹配参数,或改用内存SQLite进行轻量集成测试。

PHP 中对 PDO 进行单元测试时,不建议直接连接真实数据库,应通过 Mock 隔离外部依赖。核心思路是:不 Mock PDO 类本身(因其构造函数会触发真实连接),而是 Mock 你封装的数据库访问层(如 Repository、DAO 或 Service 类中对 PDO 的调用);若必须 Mock PDO 实例,则需借助 PHPUnit\Framework\MockObject\MockObject 并谨慎处理其方法签名与返回行为。
推荐方式:Mock 自定义数据访问类(非直接 Mock PDO)
这是最稳定、可维护性最高的做法。假设你有如下封装类:
class UserRepository {
private PDO $pdo;
<pre class="brush:php;toolbar:false;">public function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
public function findById(int $id): ?array {
$stmt = $this->pdo->prepare('SELECT * FROM users WHERE id = ?');
$stmt->execute([$id]);
return $stmt->fetch(PDO::FETCH_ASSOC);
}}
测试时,Mock UserRepository 所依赖的 PDO 实例,而非测试它内部逻辑——重点验证 SQL 执行和结果处理是否符合预期。实际操作中更推荐 Mock 其方法行为:
立即学习“PHP免费学习笔记(深入)”;
- 在测试中创建
$mockPdo = $this->createMock(PDO::class) - Mock
prepare()方法,让它返回一个预设的 Statement Mock - Mock Statement 的
execute()和fetch()行为,控制返回数据或异常 - 确保
prepare()的参数(SQL 字符串)被正确传递,可用于断言 SQL 是否符合预期
关键点:正确 Mock PDOStatement 行为
PDO 的链式调用(prepare()->execute()->fetch())要求逐层 Mock。不能只 Mock PDO::prepare() 返回一个数组,必须返回一个能响应 execute() 和 fetch() 的对象:
$mockStmt = $this->createMock(PDOStatement::class);
$mockStmt->expects($this->once())->method('execute')->willReturn(true);
$mockStmt->expects($this->once())->method('fetch')->with(PDO::FETCH_ASSOC)->willReturn(['id' => 1, 'name' => 'Alice']);
<p>$mockPdo = $this->createMock(PDO::class);
$mockPdo->expects($this->once())
->method('prepare')
->with('SELECT * FROM users WHERE id = ?')
->willReturn($mockStmt);</p>注意:fetch() 必须声明 with(PDO::FETCH_ASSOC) 参数匹配,否则 Mock 可能不生效。
替代方案:使用内存 SQLite + 数据库快照(非 Mock,但轻量可靠)
当业务逻辑复杂、Mock 成本过高时,可用 SQLite 内存数据库替代:
- 在
setUp()中执行new PDO('sqlite::memory:') - 运行建表 SQL 和初始 fixture 数据
- 每个测试用例独立事务或重建 schema,避免干扰
- 比 Mock 更贴近真实行为,适合集成边界测试
这种方式不属 Mock,但属于“可预测、无副作用”的测试策略,实践中常与 Mock 混用:核心逻辑用 Mock,涉及多表关联或事务边界时切到内存 DB。
避坑提醒
以下做法容易失败,应避免:
- 直接
createMock(PDO::class)后未 Mockprepare(),导致调用时抛出错误(因 PDO 构造已绕过,但方法未定义) - 忽略
PDOStatement::execute()返回布尔值,而 Mock 成返回数组,破坏调用链 - 在 Mock 中未声明
with()匹配参数,导致断言失效或误通过 - 试图 Mock 静态方法(如
PDO::quote())——PHPUnit 无法 Mock 静态方法,应封装到工具类再 Mock 工具类
真正需要测试的是“你的代码如何使用 PDO”,而不是 PDO 本身是否工作。把 PDO 当作不可变的协作对象,专注验证 SQL 组装、参数绑定、结果映射和异常处理逻辑即可。










