PHPUnit中如何将Mock Database对象注入到Mock ParticipantView对象?
问题:如何为测试注入带Mock数据库的ParticipantView对象?
我想测试BuildParticipant类中checkCredentials()方法的输出,先看类结构:
Class BuildParticipant { public function __construct(DBConnectionWrapper $db, ?Logger $logger=null) { $this->participant = new ParticipantView($db); // 省略其他代码 } public function build($participantId) { $credentialsOK = $this->checkCredentials($participantId, $this->participant); // 省略其他代码 } protected function checkCredentials($participantId, Crud $participant): bool { $data = $participant->find($participantId)->nextRecord(); // 基于$data做逻辑处理并返回布尔值 } }
原来的checkCredentials写法可测试性很差:
protected function checkCredentials($participantId): bool { $data = $this->participant->find($participantId)->nextRecord(); }
我改成把ParticipantView注入方法后,理论上可以MockParticipantView::find(),但构建Mock时遇到了问题:
测试代码:
<?php use PHPUnit\Framework\TestCase; class buildNewParticipantTest extends TestCase { public function testCredentialInclusion() { $mockDb = $this->getMockBuilder('Database')->getMock(); // 尝试修复测试错误但失败(ParticipantView是CrudView的子类) $participantView = new ParticipantView($mockDb); $pv = $this ->getMockBuilder($participantView::class) ->onlyMethods(['find']) ->getMock(); $pv ->expects($this->once()) ->method('find') ->willReturn([ 'id' => '1234', 'ssn' => '555-12-3456', 'lastName' => 'Smith', 'firstName' => 'John', 'credentials' => 'foo' ]); $this->assertEquals('John', $pv['firstName']); } }
运行测试后报错:
There was 1 error: 1) buildNewParticipantTest::testCredentialInclusion ArgumentCountError: Too few arguments to function CrudView::__construct(), 0 passed
补充类结构信息:
所有对象都是用于从数据库获取数据(无展示逻辑):
abstract class DBConnectionWrapper { // 类内容 }
class DB_ODBC extends DBConnectionWrapper { // 类内容 }
class Database extends DB_ODBC { // 类内容 }
class Crud implements CrudDefinition { public function __construct(DBConnectionWrapper $db, ?Monolog\Logger $log = null) { // 构造方法内容 } }
/** * CRUD View * * A CrudView accesses a query as if it were a table, so that we can * combine several tables into a single table and use Crud methods on * it. * * Read Only! */ class CrudView extends Crud { public function __construct(DBConnectionWrapper $db) { parent::__construct($db); // 其他处理逻辑... } }
class ParticipantView extends CrudView { // 类内容 }
正常实例化ParticipantView的方式:
$db = new Database(); $pv = new ParticipantView($db);
我该如何将Mock Database对象注入到Mock ParticipantView对象中?
解决方案
问题出在你构建Mock的方式上:getMockBuilder($participantView::class)会直接尝试实例化ParticipantView类,但没有传入构造方法必需的DBConnectionWrapper参数,所以触发了参数不足的错误。
正确的做法是直接针对ParticipantView类构建Mock,同时通过setConstructorArgs()传入Mock的数据库对象:
修正后的测试代码:
<?php use PHPUnit\Framework\TestCase; class buildNewParticipantTest extends TestCase { public function testCredentialInclusion() { // 1. 构建Mock数据库对象 $mockDb = $this->getMockBuilder(Database::class)->getMock(); // 2. 构建ParticipantView的Mock,传入构造参数 $pv = $this ->getMockBuilder(ParticipantView::class) ->onlyMethods(['find']) ->setConstructorArgs([$mockDb]) // 传入Mock的DB对象 ->getMock(); // 3. 处理链式调用:Mock find返回带nextRecord方法的对象 $mockRecord = $this->getMockBuilder(stdClass::class) ->addMethods(['nextRecord']) ->getMock(); $mockRecord->method('nextRecord') ->willReturn([ 'id' => '1234', 'ssn' => '555-12-3456', 'lastName' => 'Smith', 'firstName' => 'John', 'credentials' => 'foo' ]); $pv->expects($this->once()) ->method('find') ->with('1234') // 指定预期参数,增强测试严谨性 ->willReturn($mockRecord); // 4. 测试BuildParticipant的checkCredentials方法 $buildParticipant = new BuildParticipant($mockDb); // 反射调用protected方法 $reflection = new ReflectionClass($buildParticipant); $method = $reflection->getMethod('checkCredentials'); $method->setAccessible(true); $result = $method->invoke($buildParticipant, '1234', $pv); // 根据业务逻辑断言结果,比如credentials为'foo'时返回true $this->assertTrue($result); } }
关键说明:
- 构造参数传递:用
setConstructorArgs()把Mock数据库对象传入ParticipantView的Mock,解决构造方法参数不足的问题。 - 链式调用Mock:原代码中
find()->nextRecord()是链式调用,需要先让find返回一个带nextRecord方法的Mock对象,再让该方法返回测试数据。 - 访问protected方法:测试
BuildParticipant中的checkCredentials(protected方法)时,需要用PHP反射机制将方法设为可访问后再调用。
另外,你原测试中的$this->assertEquals('John', $pv['firstName']);是错误的——$pv是Mock对象,不是数组,不能用数组下标访问属性,应该通过模拟方法返回的数据来断言业务逻辑结果。
内容的提问来源于stack exchange,提问作者Tim Morton
相关产品推荐
相关产品推荐

