不能直接连真实数据库,因会导致测试变慢、不可靠、难并行;应解耦数据库逻辑,用mock对象隔离依赖,如通过createmock或getmockbuilder禁用构造函数,并严格遵循接口返回类型。

为什么不能直接在单元测试里连真实数据库
直接让 PHPUnit 测试代码调用 PDO 或 mysqli 连接真实数据库,会导致测试变慢、不可靠、难并行——比如事务没回滚干净会污染后续测试,数据库权限或网络问题会让测试随机失败。单元测试的目标是验证逻辑,不是验证 MySQL 是否装好了。
怎么用 Mock 对象隔离数据库依赖
核心思路是:把数据库操作封装进可替换的类(比如 DatabaseRepository),然后在测试中用 createMock() 或 getMockBuilder() 生成它的模拟实例,让它对 save()、find() 等方法返回预设值,而不真正发 SQL。
- 必须把数据库连接和查询逻辑从业务逻辑中解耦,否则无法 mock —— 比如不要在控制器里直接 new PDO
- mock 的方法需与被测类依赖的接口/类签名一致,否则类型检查或运行时会报错
- 用
$mock->method('getById')->willReturn($user)控制返回值;用$mock->expects($this->once())->method('save')验证是否被调用 - 如果原方法有参数校验(如
int $id),mock 时传入非法值不会触发校验——这是 mock 的代价,需靠其他测试覆盖
遇到“Cannot call constructor”错误怎么办
当你用 createMock(DatabaseConnection::class) 却报这个错,说明该类构造函数有不可 mock 的依赖(比如直接 new 了 PDO 实例,或含 require 文件、静态调用)。这时不能用 createMock(),得改用 getMockBuilder() 并禁用构造函数:
$mock = $this->getMockBuilder(DatabaseConnection::class)
->disableOriginalConstructor()
->getMock();
但更治本的做法是:把 PDO 实例作为构造参数注入,而非内部创建 —— 这样既利于测试,也符合依赖倒置原则。
Mock 返回数组还是对象?取决于你如何设计契约
如果业务层期望 findUser(int $id): User 返回一个 User 对象,mock 就该返回 new User(['name' => 'test']);如果契约是 findUser(int $id): array,mock 返回 ['name' => 'test'] 才合理。混用会导致类型不匹配、IDE 提示异常、PHP 8+ 的严格模式报错。
- PHP 7.4+ 推荐用严格返回类型声明,mock 必须遵守它
- 避免在 mock 中做复杂逻辑(比如根据参数查数组),那已经接近集成测试范畴
- 若真实数据库返回的是关联数组,而你 mock 成对象,记得检查业务代码是否用
$row['name']还是$user->name—— 错位会静默失败
beginTransaction())、哪些该忽略(比如日志记录),以及如何让 mock 行为足够贴近真实场景又不引入副作用。php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











