Magento: Singletons mit Mock-Objekten/Stubs ersetzen

Und wieder etwas zum Thema Unit Test mit Magento:

Es ist möglich, Models die über Mage::getSingleton() geladen werden zur Laufzeit mit konkreten Objekten zu ersetzen, quasi der Magento way of Dependency Injection. Dazu ist allerdings ein kleiner Trick nötig. Es gibt zwar auch die Möglichkeit, Rewrites zur Laufzeit zu konfigurieren aber im Fall von Mock Objects hilft uns das nicht weiter, schließlich wollen wir Magento nicht einfach eine andere Klasse instantiieren lassen sondern einen fertig konfigurierten Stub injizieren.

Hierzu ein gekürztes Beispiel (Test Case):

//...
	const LIFE_CLASS = 'My_Namespace_Model_Life';
	const LIFE_MODEL = 'mynamespace/life';

	private $lifeStub;
//...
	public function setUp()
	{
		parent::setUp();
		$this->registerLifeStub();
	}
	/**
	 * @test
	 */
	public function testUseBackendModel()
	{
		$this->configureLifeStub(42);
		// now test something that uses Life
	}
//...
	private function registerLifeStub()
	{
		$this->lifeStub = $this->getMock(self::LIFE_CLASS);
			
		// register stub as singleton
		$registryKey = '_singleton/'.self::LIFE_MODEL;
		Mage::unregister($registryKey);
		Mage::register($registryKey, $this->lifeStub);
	}
	private function configureLifeStub($result)
	{
		$this->lifeStub
			->expects($this->any())
			->method('getMeaning')
			->will($this->returnValue($result));
	}
//...

Die Besonderheit liegt hier in der Methode registerLifeStub(), die beim Setup aufgerufen wird, das Mock Object anlegt und registriert. Dabei wird direkt so auf die Magento-Registry zugegriffen wie es Mage::getSingleton() macht. Wichtig ist auch der Aufruf von Mage::unregister() da Mage::register() kein Überschreiben zulässt.

configureLifeStub() ist nur ein Beispiel dafür dass das Mock Object jederzeit unterschiedlich konfiguriert werden kann und dies nicht beim erstellen festgelegt werden muss.

Diese Methode ist sowohl für Core-Singletons wie die diversen Session-Klassen als auch für selbst geschriebene Provider-Klassen interessant, funktioniert aber wirklich nur, wenn diese auch mit Mage.:getSingleton() geladen werden, da Mage::getModel() immer ein neues Objekt instantiiert – dafür wäre ein deutlich tieferer Eingriff notwendig.