diff --git a/app/code/community/EcomDev/PHPUnit/Model/Fixture/Processor/Eav.php b/app/code/community/EcomDev/PHPUnit/Model/Fixture/Processor/Eav.php index 1de02921..2094b1a7 100644 --- a/app/code/community/EcomDev/PHPUnit/Model/Fixture/Processor/Eav.php +++ b/app/code/community/EcomDev/PHPUnit/Model/Fixture/Processor/Eav.php @@ -89,10 +89,15 @@ public function apply(array $data, $key, EcomDev_PHPUnit_Model_FixtureInterface $this->getResource()->beginTransaction(); foreach ($data as $entityType => $values) { - $eavLoaders[] = $this->_getEavLoader($entityType) + $eavLoaders[$entityType] = $this->_getEavLoader($entityType) ->setFixture($fixture) - ->setOptions($fixture->getOptions()) - ->loadEntity($entityType, $values); + ->setOptions($fixture->getOptions()); + + if ($eavLoaders[$entityType] instanceof EcomDev_PHPUnit_Model_Mysql4_Fixture_RestoreAwareInterface) { + $eavLoaders[$entityType]->saveData($entityType); + } + + $eavLoaders[$entityType]->loadEntity($entityType, $values); } $this->getResource()->commit(); @@ -126,16 +131,35 @@ public function discard(array $data, $key, EcomDev_PHPUnit_Model_FixtureInterfac EcomDev_PHPUnit_Model_FixtureInterface::SCOPE_SHARED); } + $typesToRestore = array(); $this->getResource()->beginTransaction(); foreach (array_keys($data) as $entityType) { + $eavLoader = $this->_getEavLoader($entityType); + if (in_array($entityType, $ignoreCleanUp)) { + if ($eavLoader instanceof EcomDev_PHPUnit_Model_Mysql4_Fixture_RestoreAwareInterface) { + $eavLoader->clearData($entityType); + } continue; } - $this->_getEavLoader($entityType) - ->cleanEntity($entityType); - } + + $eavLoader->cleanEntity($entityType); + if ($eavLoader instanceof EcomDev_PHPUnit_Model_Mysql4_Fixture_RestoreAwareInterface) { + $typesToRestore[$entityType] = $eavLoader; + } + } $this->getResource()->commit(); + + if ($typesToRestore) { + $this->getResource()->beginTransaction(); + foreach ($typesToRestore as $entityType => $eavLoader) { + $eavLoader->restoreData($entityType) + ->clearData($entityType); + } + $this->getResource()->commit(); + } + return $this; } } \ No newline at end of file diff --git a/app/code/community/EcomDev/PHPUnit/Model/Mysql4/Fixture/AbstractEav.php b/app/code/community/EcomDev/PHPUnit/Model/Mysql4/Fixture/AbstractEav.php index a24eac03..925caf20 100644 --- a/app/code/community/EcomDev/PHPUnit/Model/Mysql4/Fixture/AbstractEav.php +++ b/app/code/community/EcomDev/PHPUnit/Model/Mysql4/Fixture/AbstractEav.php @@ -22,7 +22,10 @@ */ abstract class EcomDev_PHPUnit_Model_Mysql4_Fixture_AbstractEav extends EcomDev_PHPUnit_Model_Mysql4_Fixture_AbstractComplex + implements EcomDev_PHPUnit_Model_Mysql4_Fixture_RestoreAwareInterface { + const RESTORE_KEY = 'restore_%s_data'; + /** * List of indexers required to build * @@ -37,6 +40,20 @@ abstract class EcomDev_PHPUnit_Model_Mysql4_Fixture_AbstractEav */ protected $_originalIndexers = array(); + /** + * List of tables that should be restored after run + * + * @var string[] + */ + protected $_restoreTables = array(); + + /** + * Default data for eav entity + * + * @var array + */ + protected $_defaultData = array(); + /** * Retrieve required indexers for re-building * @@ -99,6 +116,69 @@ public function cleanEntity($entityType) return $this; } + + /** + * Saves data for restoring it after fixture has been cleaned up + * + * @param string $code storage code + * @return $this + */ + public function saveData($code) + { + if ($this->_restoreTables) { + $storageKey = sprintf(self::RESTORE_KEY, $code); + $data = array(); + foreach ($this->_restoreTables as $table) { + $select = $this->_getReadAdapter()->select(); + $select->from($table); + $data[$table] = $this->_getReadAdapter()->fetchAll($select); + } + $this->_fixture->setStorageData($storageKey, $data); + } + + return $this; + } + + /** + * Restored saved data + * + * @param string $code storage code + * @return $this + */ + public function restoreData($code) + { + if ($this->_restoreTables) { + $storageKey = sprintf(self::RESTORE_KEY, $code); + $data = $this->_fixture->getStorageData($storageKey); + foreach ($this->_restoreTables as $table) { + if (!empty($data[$table])) { + $this->_getWriteAdapter()->insertOnDuplicate( + $table, + $data[$table] + ); + } + } + } + + return $this; + } + + /** + * Clears storage from stored backup data + * + * @param $code + * @return $this + */ + public function clearData($code) + { + if ($this->_restoreTables) { + $storageKey = sprintf(self::RESTORE_KEY, $code); + $this->_fixture->setStorageData($storageKey, array()); + } + + return $this; + } + /** * Loads EAV data into DB tables * @@ -139,16 +219,43 @@ public function loadEntity($entityType, $values) // and rows list as value // See getCustomTableRecords $customValues = array(); + + if ($this->_defaultData) { + $dataToInsert = $this->_defaultData; + // Prevent insertion of default data, + // if there is already data available + foreach ($values as $index => $row) { + if (isset($row[$this->_getEntityIdField($entityTypeModel)]) + && isset($dataToInsert[$this->_getEntityIdField($entityTypeModel)])) { + $dataToInsert = array(); + break; + } + } + + foreach ($dataToInsert as $row) { + array_unshift($values, $row); + } + } + - foreach ($values as $index => &$row) { + foreach ($values as $index => $row) { if (!isset($row[$this->_getEntityIdField($entityTypeModel)])) { throw new RuntimeException('Entity Id should be specified in EAV fixture'); } // Fulfill necessary information - $row['entity_type_id'] = $entityTypeModel->getEntityTypeId(); + $values[$index]['entity_type_id'] = $entityTypeModel->getEntityTypeId(); + $row = $values[$index]; + if (!isset($row['attribute_set_id'])) { - $row['attribute_set_id'] = $entityTypeModel->getDefaultAttributeSetId(); + $defaultAttributeSet = $entityTypeModel->getDefaultAttributeSetId(); + + // Fix Magento core issue with attribute set information for customer and its address + if (in_array($entityType, array('customer', 'customer_address'))) { + $defaultAttributeSet = 0; + } + + $values[$index]['attribute_set_id'] = $defaultAttributeSet; } // Preparing entity table record diff --git a/app/code/community/EcomDev/PHPUnit/Model/Mysql4/Fixture/Eav/Catalog/Category.php b/app/code/community/EcomDev/PHPUnit/Model/Mysql4/Fixture/Eav/Catalog/Category.php index 050f9c82..adcd4168 100644 --- a/app/code/community/EcomDev/PHPUnit/Model/Mysql4/Fixture/Eav/Catalog/Category.php +++ b/app/code/community/EcomDev/PHPUnit/Model/Mysql4/Fixture/Eav/Catalog/Category.php @@ -23,9 +23,36 @@ */ class EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Catalog_Category extends EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Catalog_Abstract { + const XML_PATH_DEFAULT_DATA = 'phpunit/suite/fixture/default_data/category'; + protected $_requiredIndexers = array( 'catalog_category_flat' ); + + protected function _construct() + { + parent::_construct(); + $defaultData = Mage::getConfig()->getNode(self::XML_PATH_DEFAULT_DATA); + + if ($defaultData) { + foreach ($defaultData->children() as $item) { + if (!isset($item->entity_id)) { + continue; + } + + $entityId = (string)$item->entity_id; + $this->_defaultData[$entityId] = array(); + foreach ($item->children() as $value) { + $this->_defaultData[$entityId][$value->getName()] = (string)$value; + } + } + } + + $this->_restoreTables[] = $this->getTable('catalog/category'); + foreach (array('datetime', 'decimal', 'int', 'text', 'varchar') as $suffix) { + $this->_restoreTables[] = $this->getTable(array('catalog/category', $suffix)); + } + } /** * Overridden to add easy fixture loading for product associations diff --git a/app/code/community/EcomDev/PHPUnit/Model/Mysql4/Fixture/RestoreAwareInterface.php b/app/code/community/EcomDev/PHPUnit/Model/Mysql4/Fixture/RestoreAwareInterface.php new file mode 100644 index 00000000..9568a382 --- /dev/null +++ b/app/code/community/EcomDev/PHPUnit/Model/Mysql4/Fixture/RestoreAwareInterface.php @@ -0,0 +1,28 @@ +helperMockClassAlias('model', $classAlias, $methods, $constructorArgs); } + /** + * Creates a mock for a resource model by its class alias + * + * @param string $classAlias + * @param array $methods + * @param array $constructorArgs + * + * @return EcomDev_PHPUnit_Mock_Proxy + */ + public function helperMockResourceModel($classAlias, array $methods = array(), array $constructorArgs = array()) + { + return $this->helperMockClassAlias('resource_model', $classAlias, $methods, $constructorArgs); + } + + /** * Creates a mock for a block by its class alias * diff --git a/app/code/community/EcomDev/PHPUnit/Test/Case/Util.php b/app/code/community/EcomDev/PHPUnit/Test/Case/Util.php index a04ab885..72fd9f54 100644 --- a/app/code/community/EcomDev/PHPUnit/Test/Case/Util.php +++ b/app/code/community/EcomDev/PHPUnit/Test/Case/Util.php @@ -501,7 +501,7 @@ public static function getGroupedClassName($type, $classAlias) public static function getGroupedClassMockBuilder(PHPUnit_Framework_TestCase $testCase, $type, $classAlias) { $className = self::getGroupedClassName($type, $classAlias); - return new EcomDev_PHPUnit_Mock_Proxy($testCase, $className); + return new EcomDev_PHPUnit_Mock_Proxy($testCase, $className, $classAlias); } /** @@ -511,6 +511,7 @@ public static function getGroupedClassMockBuilder(PHPUnit_Framework_TestCase $te public static function setUp() { self::app()->resetDispatchedEvents(); + self::$originalStore = Mage::app()->getStore()->getCode(); } /** diff --git a/app/code/community/EcomDev/PHPUnit/Test/Listener.php b/app/code/community/EcomDev/PHPUnit/Test/Listener.php index aa8ee806..f9d043f8 100644 --- a/app/code/community/EcomDev/PHPUnit/Test/Listener.php +++ b/app/code/community/EcomDev/PHPUnit/Test/Listener.php @@ -173,6 +173,9 @@ public function endTest(PHPUnit_Framework_Test $test, $time) )); if ($test instanceof PHPUnit_Framework_TestCase) { + EcomDev_PHPUnit_Helper::tearDown(); + EcomDev_PHPUnit_Test_Case_Util::tearDown(); + EcomDev_PHPUnit_Test_Case_Util::getFixture(get_class($test)) ->setScope(EcomDev_PHPUnit_Model_FixtureInterface::SCOPE_LOCAL) ->discard(); // Clear applied fixture @@ -180,9 +183,6 @@ public function endTest(PHPUnit_Framework_Test $test, $time) if (EcomDev_PHPUnit_Test_Case_Util::getExpectation(get_class($test))->isLoaded()) { EcomDev_PHPUnit_Test_Case_Util::getExpectation(get_class($test))->discard(); } - - EcomDev_PHPUnit_Test_Case_Util::tearDown(); - EcomDev_PHPUnit_Helper::tearDown(); } Mage::dispatchEvent('phpunit_test_end_after', array( diff --git a/app/code/community/EcomDev/PHPUnit/etc/config.xml b/app/code/community/EcomDev/PHPUnit/etc/config.xml index 0d5f02b4..d032fe57 100644 --- a/app/code/community/EcomDev/PHPUnit/etc/config.xml +++ b/app/code/community/EcomDev/PHPUnit/etc/config.xml @@ -114,6 +114,37 @@ ecomdev_phpunit/fixture_eav_catalog_product ecomdev_phpunit/fixture_eav_catalog_category + + + + 1 + 0 + 1 + 0 + 0 + 1 + Root Catalog + root-catalog + 1 + 0 + 0 + + + 2 + 1 + 1/2 + 1 + 1 + 0 + Default Category + default-category + 1 + 0 + PRODUCTS + 1 + + + diff --git a/lib/EcomDev/PHPUnit/Mock/Proxy.php b/lib/EcomDev/PHPUnit/Mock/Proxy.php index a1028ffd..53e797dc 100644 --- a/lib/EcomDev/PHPUnit/Mock/Proxy.php +++ b/lib/EcomDev/PHPUnit/Mock/Proxy.php @@ -29,6 +29,26 @@ class EcomDev_PHPUnit_Mock_Proxy { protected $mockInstance; + /** + * Original mocked class alias + * + * @var string + */ + protected $classAlias; + + /** + * Added class alias as property + * + * @param PHPUnit_Framework_TestCase $testCase + * @param array|string $type + * @param null $classAlias + */ + public function __construct(PHPUnit_Framework_TestCase $testCase, $type, $classAlias = null) + { + parent::__construct($testCase, $type); + $this->classAlias = $classAlias; + } + /** * Adds method name to a mock builder * @@ -113,6 +133,18 @@ public function expects(PHPUnit_Framework_MockObject_Matcher_Invocation $matcher return $this->getMockInstance()->expects($matcher); } + /** + * Invokes replaceByMock test util method with current mock object proxy instance + * + * @param $type + * @return $this + */ + public function replaceByMock($type) + { + EcomDev_PHPUnit_Test_Case_Util::replaceByMock($type, $this->classAlias, $this); + return $this; + } + /** * Returns invocation mocker for *