如何在PHPUnit中Mock Magento的Mage::getSingleton('checkout/cart')?
解决Magento中无法Mock
Mage::getSingleton('checkout/cart')的单元测试问题 问题描述
在为遗留Magento代码的foo()方法编写单元测试时,尝试Mock全局静态方法Mage::getSingleton('checkout/cart')失败,执行测试时触发致命错误,且无法修改源方法代码。
源方法代码
public function foo() { return Mage::getSingleton('checkout/cart')->getQuote()->getFunctionName(); }
测试函数代码
public function testFoo(): void { //$this->className :: Project_Catalog_Helper_Test $expected = 'string'; $this->className->method('getSingleton')->willReturn($this->cart); $this->cart->method('getQuote')->willReturn($this->cart); $this->cart->method('getFunctionName')->willReturn('string'); $this->assertEquals( $this->className->foo(), $expected ); }
报错日志
MacBook-Pro % ./vendor/bin/phpunit tests/src/app/code/local/project/Catalog/Helper/OrderTest.php PHPUnit 6.5.14 by Sebastian Bergmann and contributors. Runtime: PHP 7.3.11 Configuration: /project_path/phpunit.xml.dist E 1 / 1 (100%) Time: 150 ms, Memory: 8.00MB There was 1 error: 1) Project_Catalog_Helper_Test::testFoo Error: Call to a member function getCode() on bool /project_path/app/code/core/Mage/Customer/Model/Session.php:103 /project_path/app/code/core/Mage/Core/Model/Config.php:1394 /project_path/app/Mage.php:517 /project_path/app/Mage.php:531 /project_path/app/code/community/Checkout/Model/Cart.php:20 /project_path/app/code/local/project/Catalog/Helper/Order.php:21 /project_path/tests/src/app/code/local/project/Catalog/Helper/OrderTest.php:100 /project_path/vendor/phpunit/phpunit/phpunit:53
问题原因
你当前的Mock逻辑完全错误:你Mock的是测试类实例$this->className的getSingleton方法,但源方法中调用的是全局静态类Mage的getSingleton方法,二者没有关联。Mock无效导致Mage实例化了真实的Checkout_Model_Cart类,而该类初始化时依赖Mage_Customer_Model_Session,测试环境中Session初始化失败返回布尔值,进而触发Call to a member function getCode() on bool错误。
解决办法
以下两种方案均无需修改源方法代码,适配遗留代码场景:
方案1:使用PHPUnit静态Mock(适配PHPUnit 6.x)
通过PHPUnit的MockStatic直接MockMage类的静态方法:
public function testFoo(): void { $expected = 'string'; // 1. 创建Quote对象Mock,模拟目标方法返回值 $quoteMock = $this->createMock(Mage_Sales_Model_Quote::class); $quoteMock->method('getFunctionName')->willReturn($expected); // 2. 创建Cart对象Mock,模拟getQuote返回上述Quote Mock $cartMock = $this->createMock(Mage_Checkout_Model_Cart::class); $cartMock->method('getQuote')->willReturn($quoteMock); // 3. Mock Mage类的静态getSingleton方法 $mageStaticMock = $this->getMockBuilder(Mage::class) ->setMethods(['getSingleton']) ->getMockStatic(); $mageStaticMock::method('getSingleton') ->with('checkout/cart') ->willReturn($cartMock); // 执行断言 $this->assertEquals($this->className->foo(), $expected); }
方案2:使用反射强制替换Mage单例存储
若静态Mock兼容性不佳,可通过反射直接修改Mage类的单例缓存:
public function testFoo(): void { $expected = 'string'; // 1. 创建依赖的Mock对象 $quoteMock = $this->createMock(Mage_Sales_Model_Quote::class); $quoteMock->method('getFunctionName')->willReturn($expected); $cartMock = $this->createMock(Mage_Checkout_Model_Cart::class); $cartMock->method('getQuote')->willReturn($quoteMock); // 2. 用反射获取并修改Mage的$_singletons静态属性 $mageReflection = new ReflectionClass(Mage::class); $singletonsProp = $mageReflection->getProperty('_singletons'); $singletonsProp->setAccessible(true); // 将checkout/cart的单例替换为我们的Mock $originalSingletons = $singletonsProp->getValue(); $singletonsProp->setValue(array_merge($originalSingletons, [ 'checkout/cart' => $cartMock ])); // 3. 执行断言 $this->assertEquals($this->className->foo(), $expected); // 4. 清理:恢复原始单例数据,避免影响其他测试 $singletonsProp->setValue($originalSingletons); }
内容的提问来源于stack exchange,提问作者user17783631
相关产品推荐
相关产品推荐

