Commit c03bbe26 authored by Ivan Chepurnyi's avatar Ivan Chepurnyi

+ Feature #25 Implement EAV Fixture loading

    = eav type of fixture row, automatically executable indexes 
    = possibility to disable indexer run via @doNotIndexAll or @doNotIndex indexer_code
    = full Global/Website/Store support
    = full product & categories support    
+ Feature #26: Implement GWS Fixture
    = scope fixture type
    = $this->setCurrentStore() inside of the test
! Test suite application was refactored to improve absolutely isolated execution
parent 4cd00ef8
...@@ -56,6 +56,13 @@ class EcomDev_PHPUnit_Model_App extends Mage_Core_Model_App ...@@ -56,6 +56,13 @@ class EcomDev_PHPUnit_Model_App extends Mage_Core_Model_App
*/ */
protected static $_oldEventCollection = null; protected static $_oldEventCollection = null;
/**
* List of singletons in original application
*
* @var array
*/
protected static $_oldRegistry = null;
/** /**
* Configuration model class name for unit tests * Configuration model class name for unit tests
* *
...@@ -77,6 +84,13 @@ class EcomDev_PHPUnit_Model_App extends Mage_Core_Model_App ...@@ -77,6 +84,13 @@ class EcomDev_PHPUnit_Model_App extends Mage_Core_Model_App
*/ */
protected static $_cacheClass = 'EcomDev_PHPUnit_Model_Cache'; protected static $_cacheClass = 'EcomDev_PHPUnit_Model_Cache';
/**
* Enabled events flag
*
* @var boolean
*/
protected $_eventsEnabled = true;
/** /**
* This method replaces application, event and config objects * This method replaces application, event and config objects
* in Mage to perform unit tests in separate Magento steam * in Mage to perform unit tests in separate Magento steam
...@@ -88,15 +102,14 @@ class EcomDev_PHPUnit_Model_App extends Mage_Core_Model_App ...@@ -88,15 +102,14 @@ class EcomDev_PHPUnit_Model_App extends Mage_Core_Model_App
self::$_oldApplication = EcomDev_Utils_Reflection::getRestrictedPropertyValue('Mage', '_app'); self::$_oldApplication = EcomDev_Utils_Reflection::getRestrictedPropertyValue('Mage', '_app');
self::$_oldConfig = EcomDev_Utils_Reflection::getRestrictedPropertyValue('Mage', '_config'); self::$_oldConfig = EcomDev_Utils_Reflection::getRestrictedPropertyValue('Mage', '_config');
self::$_oldEventCollection = EcomDev_Utils_Reflection::getRestrictedPropertyValue('Mage', '_events'); self::$_oldEventCollection = EcomDev_Utils_Reflection::getRestrictedPropertyValue('Mage', '_events');
self::$_oldEventCollection = EcomDev_Utils_Reflection::getRestrictedPropertyValue('Mage', '_registry');
$resource = Mage::getSingleton('core/resource');
// Setting environment variables for unit tests // Setting environment variables for unit tests
EcomDev_Utils_Reflection::setRestrictedPropertyValue('Mage', '_config', new self::$_configClass); EcomDev_Utils_Reflection::setRestrictedPropertyValue('Mage', '_config', new self::$_configClass);
EcomDev_Utils_Reflection::setRestrictedPropertyValue('Mage', '_app', new self); EcomDev_Utils_Reflection::setRestrictedPropertyValue('Mage', '_app', new self);
EcomDev_Utils_Reflection::setRestrictedPropertyValue('Mage', '_events', new self::$_eventCollectionClass); EcomDev_Utils_Reflection::setRestrictedPropertyValue('Mage', '_events', new self::$_eventCollectionClass);
EcomDev_Utils_Reflection::setRestrictedPropertyValue($resource, '_connections', array()); EcomDev_Utils_Reflection::setRestrictedPropertyValue('Mage', '_registry', array());
// All unit tests will be runned in admin scope, to get rid of frontend restrictions // All unit tests will be runned in admin scope, to get rid of frontend restrictions
Mage::app()->initTest(); Mage::app()->initTest();
...@@ -113,7 +126,22 @@ class EcomDev_PHPUnit_Model_App extends Mage_Core_Model_App ...@@ -113,7 +126,22 @@ class EcomDev_PHPUnit_Model_App extends Mage_Core_Model_App
$this->_config->setOptions($options); $this->_config->setOptions($options);
$this->_initBaseConfig(); $this->_initBaseConfig();
$this->_initCache(); $this->_initCache();
// Set using cache
// for things that shouldn't be reloaded each time
EcomDev_Utils_Reflection::setRestrictedPropertyValue(
$this->_cache,
'_allowedCacheOptions',
array(
'eav' => 1,
'layout' => 1,
'translate' => 1
)
);
// Clean cache before the whole suite is running
$this->getCache()->clean(); $this->getCache()->clean();
// Init modules runs install proccess for table structures, // Init modules runs install proccess for table structures,
// It is required for setting up proper setup script // It is required for setting up proper setup script
$this->_initModules(); $this->_initModules();
...@@ -127,6 +155,18 @@ class EcomDev_PHPUnit_Model_App extends Mage_Core_Model_App ...@@ -127,6 +155,18 @@ class EcomDev_PHPUnit_Model_App extends Mage_Core_Model_App
return $this; return $this;
} }
/**
* Overriden to fix issue with stores loading
* (non-PHPdoc)
* @see Mage_Core_Model_App::_initStores()
*/
protected function _initStores()
{
$this->_store = null;
parent::_initStores();
return $this;
}
/** /**
* Discard test scope for application, returns all the objects from live version * Discard test scope for application, returns all the objects from live version
* *
...@@ -137,18 +177,44 @@ class EcomDev_PHPUnit_Model_App extends Mage_Core_Model_App ...@@ -137,18 +177,44 @@ class EcomDev_PHPUnit_Model_App extends Mage_Core_Model_App
EcomDev_Utils_Reflection::setRestrictedPropertyValue('Mage', '_app', self::$_oldApplication); EcomDev_Utils_Reflection::setRestrictedPropertyValue('Mage', '_app', self::$_oldApplication);
EcomDev_Utils_Reflection::setRestrictedPropertyValue('Mage', '_config', self::$_oldConfig); EcomDev_Utils_Reflection::setRestrictedPropertyValue('Mage', '_config', self::$_oldConfig);
EcomDev_Utils_Reflection::setRestrictedPropertyValue('Mage', '_events', self::$_oldEventCollection); EcomDev_Utils_Reflection::setRestrictedPropertyValue('Mage', '_events', self::$_oldEventCollection);
$resource = Mage::getSingleton('core/resource'); EcomDev_Utils_Reflection::setRestrictedPropertyValue('Mage', '_registry', self::$_oldRegistry);
EcomDev_Utils_Reflection::setRestrictedPropertyValue($resource, '_connections', array()); }
/**
* Disables events fire
*
* @return EcomDev_PHPUnit_Model_App
*/
public function disableEvents()
{
$this->_eventsEnabled = false;
return $this;
} }
/** /**
* We will not use cache for UnitTests * Enable events fire
* *
* @return boolean * @return EcomDev_PHPUnit_Model_App
*/ */
public function useCache($type=null) public function enableEvents()
{ {
return false; $this->_eventsEnabled = true;
return $this;
} }
/**
* Overriden for disabling events
* fire during fixutre loading
*
* (non-PHPdoc)
* @see Mage_Core_Model_App::dispatchEvent()
*/
public function dispatchEvent($eventName, $args)
{
if ($this->_eventsEnabled) {
parent::dispatchEvent($eventName, $args);
}
return $this;
}
} }
...@@ -23,6 +23,77 @@ ...@@ -23,6 +23,77 @@
*/ */
class EcomDev_PHPUnit_Model_Config extends Mage_Core_Model_Config class EcomDev_PHPUnit_Model_Config extends Mage_Core_Model_Config
{ {
/**
* Scope snapshot without applied configurations,
* It is used for proper store/website/default loading on per store basis
*
* @var Mage_Core_Model_Config_Base
*/
protected $_scopeSnapshot = null;
/**
* Load config data from DB
*
* @return Mage_Core_Model_Config
*/
public function loadDb()
{
if ($this->_isLocalConfigLoaded
&& Mage::isInstalled()
&& $this->_scopeSnapshot === null) {
$this->saveScopeSnapshot();
}
parent::loadDb();
return $this;
}
/**
* Loads scope snapshot
*
* @return EcomDev_PHPUnit_Model_Config
*/
public function loadScopeSnapshot()
{
if ($this->_scopeSnapshot === null) {
throw new RuntimeException('Cannot load scope snapshot, because it was not saved before');
}
$scopeNode = $this->_scopeSnapshot->getNode();
foreach ($scopeNode->children() as $nodeName => $values) {
// Remove somehow modified before xml node
unset($this->getNode()->$nodeName);
// Add saved snapshot of configuration node
$this->getNode()->addChild($nodeName);
$this->getNode()->$nodeName->extend($values);
}
return $this;
}
/**
* Saves current configuration snapshot,
* for pussible restoring in feature
*
* @param array $nodesToSave list of nodes for saving data, by default it is 'default', 'webistes', 'stores'
* @return EcomDev_PHPUnit_Model_Config
*/
public function saveScopeSnapshot($nodesToSave = array('default', 'websites', 'stores'))
{
$this->_scopeSnapshot = clone $this->_prototype;
$this->_scopeSnapshot->loadString('<config />');
$scopeNode = $this->_scopeSnapshot->getNode();
foreach ($nodesToSave as $node) {
$scopeNode->addChild($node);
$scopeNode->{$node}->extend(
$this->getNode($node),
true
);
}
return $this;
}
/** /**
* Loads additional configuration for unit tests * Loads additional configuration for unit tests
* (non-PHPdoc) * (non-PHPdoc)
......
...@@ -28,6 +28,15 @@ require_once 'Spyc/spyc.php'; ...@@ -28,6 +28,15 @@ require_once 'Spyc/spyc.php';
*/ */
class EcomDev_PHPUnit_Model_Fixture extends Mage_Core_Model_Abstract class EcomDev_PHPUnit_Model_Fixture extends Mage_Core_Model_Abstract
{ {
// Configuration path for eav loaders
const XML_PATH_FIXTURE_EAV_LOADERS = 'phpunit/suite/fixture/eav';
// Default eav loader class node in loaders configuration
const DEFAULT_EAV_LOADER_NODE = 'default';
// Default eav loader class alias
const DEFAULT_EAV_LOADER_CLASS = 'ecomdev_phpunit/fixture_eav_default';
/** /**
* Fixtures array, contains config, * Fixtures array, contains config,
* table and eav keys. * table and eav keys.
...@@ -59,13 +68,13 @@ class EcomDev_PHPUnit_Model_Fixture extends Mage_Core_Model_Abstract ...@@ -59,13 +68,13 @@ class EcomDev_PHPUnit_Model_Fixture extends Mage_Core_Model_Abstract
*/ */
protected $_fixture = array(); protected $_fixture = array();
/** /**
* Associative array of configuration values that was changed by fixture, * Fixture options
* it is used to preserve
* *
* @var array * @var array
*/ */
protected $_originalConfiguration = array(); protected $_options = array();
/** /**
* Associative array of configuration nodes xml that was changed by fixture, * Associative array of configuration nodes xml that was changed by fixture,
...@@ -75,6 +84,14 @@ class EcomDev_PHPUnit_Model_Fixture extends Mage_Core_Model_Abstract ...@@ -75,6 +84,14 @@ class EcomDev_PHPUnit_Model_Fixture extends Mage_Core_Model_Abstract
*/ */
protected $_originalConfigurationXml = array(); protected $_originalConfigurationXml = array();
/**
* Hash of current scope instances (store, website, group)
*
* @return array
*/
protected $_currentScope = array();
/** /**
* Model constuctor, just defines wich resource model to use * Model constuctor, just defines wich resource model to use
* (non-PHPdoc) * (non-PHPdoc)
...@@ -85,6 +102,18 @@ class EcomDev_PHPUnit_Model_Fixture extends Mage_Core_Model_Abstract ...@@ -85,6 +102,18 @@ class EcomDev_PHPUnit_Model_Fixture extends Mage_Core_Model_Abstract
$this->_init('ecomdev_phpunit/fixture'); $this->_init('ecomdev_phpunit/fixture');
} }
/**
* Set fixture options
*
* @param array $options
* @return EcomDev_PHPUnit_Model_Fixture
*/
public function setOptions(array $options)
{
$this->_options = $options;
return $this;
}
/** /**
* Load YAML file * Load YAML file
* *
...@@ -152,47 +181,12 @@ class EcomDev_PHPUnit_Model_Fixture extends Mage_Core_Model_Abstract ...@@ -152,47 +181,12 @@ class EcomDev_PHPUnit_Model_Fixture extends Mage_Core_Model_Abstract
if (!is_array($configuration)) { if (!is_array($configuration)) {
throw new InvalidArgumentException('Configuration part should be an associative list'); throw new InvalidArgumentException('Configuration part should be an associative list');
} }
Mage::getConfig()->loadScopeSnapshot();
foreach ($configuration as $path => $value) { foreach ($configuration as $path => $value) {
$this->_originalConfiguration[$path] = (string) Mage::getConfig()->getNode($path);
$this->_setConfigNodeValue($path, $value); $this->_setConfigNodeValue($path, $value);
} }
Mage::getConfig()->loadDb();
return $this; Mage::app()->reinitStores();
}
/**
* Setting config value with applying the values to stores and websites
*
* @param string $path
* @param string $value
* @return EcomDev_PHPUnit_Model_Fixture
*/
protected function _setConfigNodeValue($path, $value)
{
$pathArray = explode('/', $path);
$scope = array_shift($pathArray);
switch ($scope) {
case 'stores':
$storeCode = array_shift($pathArray);
Mage::app()->getStore($storeCode)->setConfig(
implode('/', $pathArray), $value
);
break;
case 'websites':
$websiteCode = array_shift($pathArray);
Mage::app()->getWebsite($websiteCode)->setConfig(
implode('/', $pathArray), $value
);
break;
default:
Mage::getConfig()->setNode($path, $value);
break;
}
return $this; return $this;
} }
...@@ -238,13 +232,12 @@ class EcomDev_PHPUnit_Model_Fixture extends Mage_Core_Model_Abstract ...@@ -238,13 +232,12 @@ class EcomDev_PHPUnit_Model_Fixture extends Mage_Core_Model_Abstract
*/ */
protected function _discardConfig() protected function _discardConfig()
{ {
foreach ($this->_originalConfiguration as $path => $value) { Mage::getConfig()->loadScopeSnapshot();
$this->_setConfigNodeValue($path, $value); Mage::getConfig()->loadDb();
} Mage::app()->reinitStores();
$this->_originalConfiguration = array();
return $this; return $this;
} }
/** /**
* Reverts fixture configuration xml values in Mage_Core_Model_Config * Reverts fixture configuration xml values in Mage_Core_Model_Config
* *
...@@ -306,10 +299,165 @@ class EcomDev_PHPUnit_Model_Fixture extends Mage_Core_Model_Abstract ...@@ -306,10 +299,165 @@ class EcomDev_PHPUnit_Model_Fixture extends Mage_Core_Model_Abstract
} }
/** /**
* @todo Create Implementation for EAV models * Setting config value with applying the values to stores and websites
*
* @param string $path
* @param string $value
* @return EcomDev_PHPUnit_Model_Fixture
*/
protected function _setConfigNodeValue($path, $value)
{
$pathArray = explode('/', $path);
$scope = array_shift($pathArray);
switch ($scope) {
case 'stores':
$storeCode = array_shift($pathArray);
Mage::app()->getStore($storeCode)->setConfig(
implode('/', $pathArray), $value
);
break;
case 'websites':
$websiteCode = array_shift($pathArray);
$website = Mage::app()->getWebsite($websiteCode);
$website->setConfig(implode('/', $pathArray), $value);
break;
default:
Mage::getConfig()->setNode($path, $value);
break;
}
return $this;
}
/**
* Retrieves eav loader for a particular entity type
*
* @param string $entityType
* @return EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Abstract
*/ */
protected function _getEavLoader($entityType)
{
$loaders = Mage::getConfig()->getNode(self::XML_PATH_FIXTURE_EAV_LOADERS);
if (isset($loaders->$entityType)) {
$classAlias = (string)$loaders->$entityType;
} elseif (isset($loaders->{self::DEFAULT_EAV_LOADER_NODE})) {
$classAlias = (string)$loaders->{self::DEFAULT_EAV_LOADER_NODE};
} else {
$classAlias = self::DEFAULT_EAV_LOADER_CLASS;
}
return Mage::getResourceSingleton($classAlias);
}
/** /**
* @todo Create Implementation for Websites/Stores/Groups * Applies fixture EAV values
*
* @param array $configuration
* @return EcomDev_PHPUnit_Model_Fixture
*/ */
protected function _applyEav($entities)
{
if (!is_array($entities)) {
throw new InvalidArgumentException('EAV part should be an associative list with rows as value and entity type as key');
}
foreach ($entities as $entityType => $values) {
$this->_getEavLoader($entityType)
->setOptions($this->_options)
->loadEntity($entityType, $values);
}
return $this;
}
/**
* Clean applied eav data
*
* @param array $entities
* @return EcomDev_PHPUnit_Model_Fixture
*/
protected function _discardEav($entities)
{
foreach (array_keys($entities) as $entityType) {
$this->_getEavLoader($entityType)
->cleanEntity($entityType);
}
return $this;
}
/**
* Applies scope fixture,
* i.e., website, store, store group
*
* @param array $types
* @return EcomDev_PHPUnit_Model_Fixture
*/
protected function _applyScope($types)
{
$modelByType = array(
'store' => 'core/store',
'group' => 'core/store_group',
'website' => 'core/website'
);
Mage::app()->disableEvents();
foreach ($types as $type => $rows) {
if (!isset($modelByType[$type])) {
throw new RuntimeException(sprintf('Unknown "%s" scope type specified', $type));
}
foreach ($rows as $row) {
$scopeModel = Mage::getModel($modelByType[$type]);
$this->_currentScope[$type][] = $scopeModel;
$scopeModel->setData($row);
// Change property for saving new objects with specified ids
EcomDev_Utils_Reflection::setRestrictedPropertyValue(
$scopeModel->getResource(), '_useIsObjectNew', true
);
$scopeModel->isObjectNew(true);
$scopeModel->save();
// Revert changed property
EcomDev_Utils_Reflection::setRestrictedPropertyValue(
$scopeModel->getResource(), '_useIsObjectNew', false
);
}
}
Mage::app()->enableEvents();
Mage::app()->reinitStores();
return $this;
}
/**
* Removes scope fixture changes,
* i.e., website, store, store group
*
* @return EcomDev_PHPUnit_Model_Fixture
*/
protected function _discardScope()
{
Mage::app()->disableEvents();
$scope = array_reverse($this->_currentScope);
foreach ($scope as $models) {
foreach ($models as $model) {
$model->delete();
}
}
$this->_currentScope = array();
Mage::app()->getCache()->clean(
Zend_Cache::CLEANING_MODE_MATCHING_TAG,
array(Mage_Core_Model_Mysql4_Collection_Abstract::CACHE_TAG)
);
Mage::app()->enableEvents();
Mage::app()->reinitStores();
return $this;
}
} }
<?php
/**
* PHP Unit test suite for Magento
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://opensource.org/licenses/osl-3.0.php
*
* @category EcomDev
* @package EcomDev_PHPUnit
* @copyright Copyright (c) 2011 Ecommerce Developers (http://www.ecomdev.org)
* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
* @author Ivan Chepurnyi <ivan.chepurnyi@ecomdev.org>
*/
/**
* Base Catalog EAV fixture loader
*
*
*/
abstract class EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Catalog_Abstract extends EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Abstract
{
const SCOPE_TYPE_STORE = 'stores';
const SCOPE_TYPE_WEBSITE = 'websites';
/**
* Overriden to add GWS implementation for attribute records
*
* @param array $row
* @param Mage_Catalog_Model_Resource_Eav_Attribute $attribute
* @param array $tableColumns
* @return array
* (non-PHPdoc)
* @see EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Abstract::_getAttributeRecords()
*/
protected function _getAttributeRecords($row, $attribute, $tableColumns)
{
$records = parent::_getAttributeRecords($row, $attribute, $tableColumns);
// If the attribute is not global,
// then walk over all websites and stores scopes for attribute value
if ($attribute->isScopeStore() || $attribute->isScopeWebsite()) {
// Search for website values and fullfil data per website's store
$storeValues = array();
foreach ($this->_getGwsCodes($row, self::SCOPE_TYPE_WEBSITE) as $websiteCode) {
$website = Mage::app()->getWebsite($websiteCode);
$value = $this->_getGwsValue($row, $attribute, $websiteCode, self::SCOPE_TYPE_WEBSITE);
if ($value !== null) {
foreach ($website->getStoreIds() as $storeId) {
$storeValues[$storeId] = $value;
}
}
}
// If attribute has store scope, then override website values by store ones
if ($attribute->isScopeStore()) {
foreach ($this->_getGwsCodes($row, self::SCOPE_TYPE_STORE) as $storeCode) {
$store = Mage::app()->getStore($storeCode);
$value = $this->_getGwsValue($row, $attribute, $storeCode, self::SCOPE_TYPE_STORE);
if ($value !== null) {
$storeValues[$store->getId()] = $value;
}
}
}
// Apply collected values
$valueInfo = $this->_getAttributeValueInfo($row, $attribute);
foreach ($storeValues as $storeId => $value) {
$valueInfo['store_id'] = $storeId;
$valueInfo['value'] = $value;
$records[] = $this->_getTableRecord($valueInfo, $tableColumns);
}
}
return $records;
}
/**
* Check is available store/website values
*
* @param array $row
* @param string $scopeType
* @return boolean
*/
protected function _hasGwsValues($row, $scopeType = self::SCOPE_TYPE_STORE)
{
return isset($row['/' . $scopeType]);
}
/**
* Retrieves list of websites/stores codes
*
* @param array $row
* @param string $scopeType
*/
protected function _getGwsCodes($row, $scopeType = self::SCOPE_TYPE_STORE)
{
if (!$this->_hasGwsValues($row, $scopeType)) {
return array();
}
return array_keys($row['/' . $scopeType]);
}
/**
* Retrieves scope dependent value from fixture value, i.e,
* store view or
* website attribute value
*
*
* @param array $row
* @param Mage_Eav_Model_Entity_Attribute $attribute
* @param string $scopeCode
* @param string $scopeType
* @return mixed|null
*/
protected function _getGwsValue($row, $attribute, $scopeCode, $scopeType = self::SCOPE_TYPE_STORE)
{
if (!isset($row['/' . $scopeType][$scopeCode]) || !is_array($row['/' . $scopeType][$scopeCode])) {
return null;
}
return $this->_getAttributeValue($row['/' . $scopeType][$scopeCode], $attribute);
}
/**
* Overriden to add default store id
* (non-PHPdoc)
* @see EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Abstract::_getAttributeValueInfo()
*/
protected function _getAttributeValueInfo($row, $attribute)
{
$info = parent::_getAttributeValueInfo($row, $attribute);
$info['store_id'] = 0;
return $info;
}
}
<?php
/**
* PHP Unit test suite for Magento
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://opensource.org/licenses/osl-3.0.php
*
* @category EcomDev
* @package EcomDev_PHPUnit
* @copyright Copyright (c) 2011 Ecommerce Developers (http://www.ecomdev.org)
* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
* @author Ivan Chepurnyi <ivan.chepurnyi@ecomdev.org>
*/
/**
* Category EAV fixture loader
*
*
*/
class EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Catalog_Category extends EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Catalog_Abstract
{
protected $_requiredIndexers = array(
'catalog_category_flat'
);
/**
* Overriden to add easy fixture loading for product associations
* (non-PHPdoc)
* @see EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Abstract::_getCustomTableRecords()
*/
protected function _getCustomTableRecords($row, $entityTypeModel)
{
return $this->_getProductAssociationRecords($row, $entityTypeModel);
}
/**
* Generates records for catalog_category_product table
*
* @param array $row
* @param Mage_Eav_Model_Entity_Type $entityTypeModel
* @return array
*/
protected function _getProductAssociationRecords($row, $entityTypeModel)
{
if (isset($row['products']) && is_array($row['products'])) {
$records = array();
foreach ($row['products'] as $productId => $position) {
$records[] = array(
'category_id' => $row[$this->_getEntityIdField($entityTypeModel)],
'product_id' => $productId,
'position' => $position
);
}
if ($records) {
$this->addRequiredIndexer('catalog_category_product');
return array('catalog/category_product' => $records);
}
}
return array();
}
}
\ No newline at end of file
<?php
/**
* PHP Unit test suite for Magento
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://opensource.org/licenses/osl-3.0.php
*
* @category EcomDev
* @package EcomDev_PHPUnit
* @copyright Copyright (c) 2011 Ecommerce Developers (http://www.ecomdev.org)
* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
* @author Ivan Chepurnyi <ivan.chepurnyi@ecomdev.org>
*/
/**
* Product EAV fixture loader
*
*
*/
class EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Catalog_Product extends EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Catalog_Abstract
{
protected $_requiredIndexers = array(
'cataloginventory_stock',
'catalog_product_flat',
'catalog_product_attribute',
'catalog_product_price'
);
/**
* Ovveriden to fix issue with flat tables existance mark
* (non-PHPdoc)
* @see EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Abstract::loadEntity()
*/
public function loadEntity($entityType, $values)
{
// Fix of Product Flat Indexer
EcomDev_Utils_Reflection::setRestrictedPropertyValue(
Mage::getResourceSingleton('catalog/product_flat_indexer'),
'_existsFlatTables',
array()
);
return parent::loadEntity($entityType, $values);
}
/**
* Overriden to add easy fixture loading for websites and categories associations
* (non-PHPdoc)
* @see EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Abstract::_getCustomTableRecords()
*/
protected function _getCustomTableRecords($row, $entityTypeModel)
{
$records = array();
$records += $this->_getWebsiteVisibilityRecords($row, $entityTypeModel);
$records += $this->_getTierPriceRecords($row, $entityTypeModel);
$records += $this->_getCategoryAssociationRecords($row, $entityTypeModel);
$records += $this->_getProductStockRecords($row, $entityTypeModel);
return $records;
}
/**
* Changed to support price attribute type multi-scope
* (non-PHPdoc)
* @param Mage_Eav_Model_Entity_Attribute $attribute
* @see EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Catalog_Abstract::_getAttributeRecords()
*/
protected function _getAttributeRecords($row, $attribute, $tableColumns)
{
if ($attribute->getFrontendInput() == 'price') {
$attribute->setIsGlobal(
Mage_Catalog_Model_Resource_Eav_Attribute::SCOPE_WEBSITE
);
}
return parent::_getAttributeRecords($row, $attribute, $tableColumns);
}
/**
* Generates records for catalog_product_website table
*
* @param array $row
* @param Mage_Eav_Model_Entity_Type $entityTypeModel
* @return array
* @throws RuntimeException
*/
protected function _getWebsiteVisibilityRecords($row, $entityTypeModel)
{
if (isset($row['website_ids']) && is_array($row['website_ids'])) {
$records = array();
foreach ($row['website_ids'] as $websiteId) {
$website = Mage::app()->getWebsite($websiteId);
$records[] = array(
'product_id' => $row[$this->_getEntityIdField($entityTypeModel)],
'website_id' => $website->getId()
);
}
// We shouldn't return empty table data
if ($records) {
return array('catalog/product_website' => $records);
}
}
return array();
}
/**
* Generates records for catalog_product_entity_tier_price table
*
* @param array $row
* @param Mage_Eav_Model_Entity_Type $entityTypeModel
* @return array
*/
protected function _getTierPriceRecords($row, $entityTypeModel)
{
if (isset($row['tier_price']) && is_array($row['tier_price'])) {
$tableName = $entityTypeModel->getValueTablePrefix() . '_tier_price';
$columns = $this->_getWriteAdapter()->describeTable(
$tableName
);
$records = array();
foreach ($row['tier_price'] as $tierPrice) {
$tierPrice[$this->_getEntityIdField($entityTypeModel)] = $row[$this->_getEntityIdField($entityTypeModel)];
$records[] = $this->_getTableRecord($tierPrice, $columns);
}
if ($records) {
return array($tableName => $records);
}
}
return array();
}
/**
* Generates records for catalog_category_product table
*
* @param array $row
* @param Mage_Eav_Model_Entity_Type $entityTypeModel
* @return array
*/
protected function _getCategoryAssociationRecords($row, $entityTypeModel)
{
if (isset($row['category_ids']) && is_array($row['category_ids'])) {
$records = array();
foreach ($row['category_ids'] as $categoryId) {
$records[] = array(
'category_id' => $categoryId,
'product_id' => $row[$this->_getEntityIdField($entityTypeModel)]
);
}
if ($records) {
$this->addRequiredIndexer('catalog_category_product');
return array('catalog/category_product' => $records);
}
}
return array();
}
/**
* Generates records for cataloginventory_stock_item table
*
* @param array $row
* @param Mage_Eav_Model_Entity_Type $entityTypeModel
* @return array
*/
protected function _getProductStockRecords($row, $entityTypeModel)
{
if (isset($row['stock']) && is_array($row['stock'])) {
$columns = $this->_getWriteAdapter()->describeTable(
$this->getTable('cataloginventory/stock_item')
);
$row['stock']['product_id'] = $row[$this->_getEntityIdField($entityTypeModel)];
if (!isset($row['stock']['stock_id'])) {
$row['stock']['stock_id'] = 1;
}
return array(
'cataloginventory/stock_item' => array(
$this->_getTableRecord($row['stock'], $columns)
)
);
}
return array();
}
/**
* Adding enabled and visibility indexes
*
* (non-PHPdoc)
* @see EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Abstract::_customEntityAction()
*/
protected function _customEntityAction($entity, $entityTypeModel)
{
Mage::getResourceSingleton('catalog/product_status')
->refreshEnabledIndex($entity[$this->_getEntityIdField($entityTypeModel)], 0);
parent::_customEntityAction($entity, $entityTypeModel);
return $this;
}
}
\ No newline at end of file
<?php
/**
* PHP Unit test suite for Magento
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://opensource.org/licenses/osl-3.0.php
*
* @category EcomDev
* @package EcomDev_PHPUnit
* @copyright Copyright (c) 2011 Ecommerce Developers (http://www.ecomdev.org)
* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
* @author Ivan Chepurnyi <ivan.chepurnyi@ecomdev.org>
*/
/**
* Default EAV fixture loaded
*
*
*/
class EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Default extends EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Abstract
{
}
\ No newline at end of file
...@@ -34,6 +34,14 @@ abstract class EcomDev_PHPUnit_Test_Case extends PHPUnit_Framework_TestCase ...@@ -34,6 +34,14 @@ abstract class EcomDev_PHPUnit_Test_Case extends PHPUnit_Framework_TestCase
*/ */
protected $_expectations = null; protected $_expectations = null;
/**
* Original store kept for tearDown,
* if was set in test method
*
* @var Mage_Core_Model_Store
*/
protected $_originalStore = null;
/** /**
* Loads expectations for current test case * Loads expectations for current test case
* *
...@@ -90,7 +98,7 @@ abstract class EcomDev_PHPUnit_Test_Case extends PHPUnit_Framework_TestCase ...@@ -90,7 +98,7 @@ abstract class EcomDev_PHPUnit_Test_Case extends PHPUnit_Framework_TestCase
if (!is_array($dataPart)) { if (!is_array($dataPart)) {
throw new InvalidArgumentException( throw new InvalidArgumentException(
'Argument values for specifying special scope of expectations should be presented ' 'Argument values for specifying special scope of expectations should be presented '
. ' in expectation file and should be an associative list' . ' in expectation file and should be an associative list (dataKey: "' . $dataKey . '")'
); );
} }
...@@ -168,7 +176,8 @@ abstract class EcomDev_PHPUnit_Test_Case extends PHPUnit_Framework_TestCase ...@@ -168,7 +176,8 @@ abstract class EcomDev_PHPUnit_Test_Case extends PHPUnit_Framework_TestCase
$this->_getFixture()->loadYaml($filePath); $this->_getFixture()->loadYaml($filePath);
} }
} }
// Pass methods for
$this->_getFixture()->setOptions($annotations['method']);
$this->_getFixture()->apply(); $this->_getFixture()->apply();
parent::setUp(); parent::setUp();
} }
...@@ -192,6 +201,24 @@ abstract class EcomDev_PHPUnit_Test_Case extends PHPUnit_Framework_TestCase ...@@ -192,6 +201,24 @@ abstract class EcomDev_PHPUnit_Test_Case extends PHPUnit_Framework_TestCase
return Spyc::YAMLLoad($filePath); return Spyc::YAMLLoad($filePath);
} }
/**
* Set current store scope for test
*
* @param int|string|Mage_Core_Model_Store $store
* @return EcomDev_PHPUnit_Test_Case
*/
public function setCurrentStore($store)
{
if (!$this->_originalStore) {
$this->_originalStore = Mage::app()->getStore();
}
Mage::app()->setCurrentStore(
Mage::app()->getStore($store)
);
return $this;
}
/** /**
* Performs a clean up after a particular test was run * Performs a clean up after a particular test was run
* (non-PHPdoc) * (non-PHPdoc)
...@@ -199,6 +226,11 @@ abstract class EcomDev_PHPUnit_Test_Case extends PHPUnit_Framework_TestCase ...@@ -199,6 +226,11 @@ abstract class EcomDev_PHPUnit_Test_Case extends PHPUnit_Framework_TestCase
*/ */
protected function tearDown() protected function tearDown()
{ {
if ($this->_originalStore) { // Remove store scope, that was set in test
Mage::app()->setCurrentStore($this->_originalStore);
$this->_originalStore = null;
}
$this->_expectations = null; // Clear expectation values $this->_expectations = null; // Clear expectation values
$this->_getFixture()->discard(); // Clear applied fixture $this->_getFixture()->discard(); // Clear applied fixture
parent::tearDown(); parent::tearDown();
......
...@@ -29,6 +29,8 @@ class EcomDev_PHPUnit_Test_Suite extends PHPUnit_Framework_TestSuite ...@@ -29,6 +29,8 @@ class EcomDev_PHPUnit_Test_Suite extends PHPUnit_Framework_TestSuite
const XML_PATH_UNIT_TEST_GROUPS = 'phpunit/suite/groups'; const XML_PATH_UNIT_TEST_GROUPS = 'phpunit/suite/groups';
const XML_PATH_UNIT_TEST_MODULES = 'phpunit/suite/modules'; const XML_PATH_UNIT_TEST_MODULES = 'phpunit/suite/modules';
const XML_PATH_UNIT_TEST_APP = 'phpunit/suite/app'; const XML_PATH_UNIT_TEST_APP = 'phpunit/suite/app';
const CACHE_TAG = 'ECOMDEV_PHPUNIT';
const CACHE_TYPE = 'ecomdev_phpunit';
/** /**
* Setting up test scope for Magento * Setting up test scope for Magento
...@@ -86,47 +88,82 @@ class EcomDev_PHPUnit_Test_Suite extends PHPUnit_Framework_TestSuite ...@@ -86,47 +88,82 @@ class EcomDev_PHPUnit_Test_Suite extends PHPUnit_Framework_TestSuite
continue; continue;
} }
$directoryIterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($searchPath)
);
$currentGroups = array( $currentGroups = array(
$group->getName(), $group->getName(),
$module->getName() $module->getName()
); );
foreach ($directoryIterator as $fileObject) { $testCases = self::_loadTestCases($searchPath, $moduleCodeDir);
/* @var $fileObject SplFileObject */
// Skip entry if it is not a php file foreach ($testCases as $className) {
if (!$fileObject->isFile() || $fileObject->getBasename('.php') === $fileObject->getBasename()) { $classReflection = EcomDev_Utils_Reflection::getRelflection($className);
continue; $suite->addTest(new PHPUnit_Framework_TestSuite($classReflection), $currentGroups);
} }
}
}
if (!$suite->count()) {
$suite->addTest(self::warning('There were no test cases for the current run'));
}
return $suite;
}
/**
* Loads test cases from search path,
* Will return cached result
*
* @param string $searchPath path for searching files with tests
* @param string $moduleCodeDir path where the module files are placed (e.g. app/code/local),
* used for determining the class name
*/
protected static function _loadTestCases($searchPath, $moduleCodeDir)
{
if (Mage::app()->useCache(self::CACHE_TYPE)) {
$cachedTestCases = Mage::app()->loadCache(
self::CACHE_TYPE . md5($searchPath)
);
if ($cachedTestCases) {
return unserialize($cachedTestCases);
}
}
$testCases = array();
$classPath = substr($fileObject->getPath() . DS . $fileObject->getBasename('.php'), strlen($moduleCodeDir)); $directoryIterator = new RecursiveIteratorIterator(
$className = uc_words(ltrim($classPath, DS), '_', DS); new RecursiveDirectoryIterator($searchPath)
);
// Add unit test case only foreach ($directoryIterator as $fileObject) {
// if it is a valid class extended from EcomDev_PHPUnit_Test_Case /* @var $fileObject SplFileObject */
if (class_exists($className, true)) { // Skip entry if it is not a php file
if (!$fileObject->isFile() || $fileObject->getBasename('.php') === $fileObject->getBasename()) {
continue;
}
$reflectionClass = EcomDev_Utils_Reflection::getRelflection($className); $classPath = substr($fileObject->getPath() . DS . $fileObject->getBasename('.php'), strlen($moduleCodeDir));
if (!$reflectionClass->isSubclassOf('EcomDev_PHPUnit_Test_Case') $className = uc_words(ltrim($classPath, DS), '_', DS);
|| $reflectionClass->isAbstract()) {
continue;
}
$suite->addTest(new PHPUnit_Framework_TestSuite($reflectionClass), $currentGroups); // Add unit test case only
} // if it is a valid class extended from EcomDev_PHPUnit_Test_Case
if (class_exists($className, true)) {
$reflectionClass = EcomDev_Utils_Reflection::getRelflection($className);
if (!$reflectionClass->isSubclassOf('EcomDev_PHPUnit_Test_Case')
|| $reflectionClass->isAbstract()) {
continue;
} }
$testCases[] = $className;
} }
} }
if (!$suite->count()) { if (Mage::app()->useCache(self::CACHE_TYPE)) {
$suite->addTest(self::warning('There were no test cases for the current run')); Mage::app()->saveCache(
serialize($testCases),
self::CACHE_TYPE . md5($searchPath),
array(self::CACHE_TAG)
);
} }
return $suite; return $testCases;
} }
} }
...@@ -33,6 +33,15 @@ ...@@ -33,6 +33,15 @@
<class>EcomDev_PHPUnit_Model_Mysql4</class> <class>EcomDev_PHPUnit_Model_Mysql4</class>
</ecomdev_phpunit_mysql4> </ecomdev_phpunit_mysql4>
</models> </models>
<cache>
<types>
<ecomdev_phpunit>
<label>Unit Test Cases</label>
<description>Unit test case file paths cache.</description>
<tags>ECOMDEV_PHPUNIT</tags>
</ecomdev_phpunit>
</types>
</cache>
</global> </global>
<phpunit> <phpunit>
<suite> <suite>
...@@ -42,6 +51,16 @@ ...@@ -42,6 +51,16 @@
<helpers>Helper</helpers> <helpers>Helper</helpers>
<blocks>Block</blocks> <blocks>Block</blocks>
</groups> </groups>
<fixture>
<eav>
<!-- Here goes the list of fixture loaders for EAV
If no fixture loader is specified for entity, then default will be used
-->
<default>ecomdev_phpunit/fixture_eav_default</default>
<catalog_product>ecomdev_phpunit/fixture_eav_catalog_product</catalog_product>
<catalog_category>ecomdev_phpunit/fixture_eav_catalog_category</catalog_category>
</eav>
</fixture>
<!-- Application model class name for running tests --> <!-- Application model class name for running tests -->
<app>EcomDev_PHPUnit_Model_App</app> <app>EcomDev_PHPUnit_Model_App</app>
<modules> <modules>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment