Magento 1.9无DI环境下构造函数中受保护类的模拟测试方法
嘿,针对你在Magento 1.9里搞GDPR相关代码测试时遇到的难题——没法模拟构造函数里实例化的受保护类,我来分享几个实用的解决办法,毕竟Magento 1.x确实没DI容器,得用些老派但靠谱的技巧:
方案1:用PHPUnit反射机制直接替换(无需改业务代码)
Magento 1.x的很多类都会在构造函数里直接new依赖或者用Mage::getModel()实例化受保护属性,这时候反射就能帮我们绕过访问限制,把这些受保护的实例换成模拟对象。
举个例子,假设你要测试的GDPR客户类是这样的:
class Your_Gdpr_Model_Customer extends Mage_Core_Model_Abstract { protected $_emailValidator; public function __construct() { $this->_emailValidator = Mage::getModel('core/email_validator'); } public function isEmailCompliant($customerEmail) { return $this->_emailValidator->validate($customerEmail); } }
测试时用反射替换受保护的验证器:
public function testIsEmailCompliant() { // 初始化目标测试类 $customerModel = Mage::getModel('your_gdpr/customer'); // 创建模拟的邮箱验证器,禁用原构造函数 $mockValidator = $this->getMockBuilder('Mage_Core_Model_Email_Validator') ->disableOriginalConstructor() ->getMock(); // 模拟验证通过的返回值 $mockValidator->method('validate') ->willReturn(true); // 用反射打开受保护属性的访问权限 $reflection = new ReflectionClass($customerModel); $emailValidatorProperty = $reflection->getProperty('_emailValidator'); $emailValidatorProperty->setAccessible(true); // 把模拟对象赋值给受保护属性 $emailValidatorProperty->setValue($customerModel, $mockValidator); // 执行断言 $this->assertTrue($customerModel->isEmailCompliant('test@example.com')); }
这个方法的优势是完全不用动业务代码,直接在测试层搞定,适合不想触碰生产代码的场景。
方案2:重构业务代码,给构造函数加注入入口(长期最优实践)
如果允许微调业务代码,最好给构造函数加一个可选参数,用来注入依赖——这样既不影响原有生产环境的调用逻辑,又能让测试变得非常简单:
修改后的业务类:
class Your_Gdpr_Model_Customer extends Mage_Core_Model_Abstract { protected $_emailValidator; // 新增可选参数,测试时传入模拟对象,生产环境用默认值 public function __construct($emailValidator = null) { $this->_emailValidator = $emailValidator ?: Mage::getModel('core/email_validator'); } // ... 原有业务逻辑不变 }
测试代码就变得清爽很多:
public function testIsEmailCompliant() { $mockValidator = $this->getMockBuilder('Mage_Core_Model_Email_Validator') ->disableOriginalConstructor() ->getMock(); $mockValidator->method('validate') ->willReturn(true); // 直接把模拟对象传入构造函数 $customerModel = Mage::getModel('your_gdpr/customer', ['emailValidator' => $mockValidator]); $this->assertTrue($customerModel->isEmailCompliant('test@example.com')); }
这种方式更符合可测试性的设计原则,虽然要改几行代码,但对代码质量的提升是长期的,也是我最推荐的方案。
方案3:利用Magento配置重写(全局替换场景)
如果你的依赖是通过Mage::getModel()/Mage::helper()获取的,还可以在测试的setUp方法里临时重写Magento配置,让它返回你的模拟对象:
protected function setUp(): void { parent::setUp(); // 创建模拟验证器 $mockValidator = $this->getMockBuilder('Mage_Core_Model_Email_Validator') ->disableOriginalConstructor() ->getMock(); $mockValidator->method('validate') ->willReturn(true); // 临时重写core/email_validator的模型类为我们的模拟类 Mage::getConfig()->setNode('global/models/core/email_validator/class', get_class($mockValidator)); // 如果是单例模式,先清除已注册的实例 Mage::unregister('_singleton/core/email_validator'); } protected function tearDown(): void { // 测试结束后恢复原配置,避免影响其他用例 Mage::getConfig()->setNode('global/models/core/email_validator/class', 'Mage_Core_Model_Email_Validator'); Mage::unregister('_singleton/core/email_validator'); parent::tearDown(); }
这个方法适合需要全局替换依赖的场景,但要注意测试隔离的问题,一定要在tearDown里恢复原配置,不然会干扰其他测试用例的结果。
内容的提问来源于stack exchange,提问作者shineability
相关产品推荐
相关产品推荐

