From 45c8fbf0a2e71c0319784d003374af0a2495853f Mon Sep 17 00:00:00 2001 From: Fabian Bircher Date: Mon, 5 Oct 2020 23:20:44 +0200 Subject: [PATCH] Issue #3173393 by bircher: Add test traits to harmonize tests --- tests/src/Kernel/ConfigStorageTestTrait.php | 146 ++++++++++++++++++ tests/src/Kernel/ExampleStorageKernelTest.php | 81 ++++++++++ 2 files changed, 227 insertions(+) create mode 100644 tests/src/Kernel/ConfigStorageTestTrait.php create mode 100644 tests/src/Kernel/ExampleStorageKernelTest.php diff --git a/tests/src/Kernel/ConfigStorageTestTrait.php b/tests/src/Kernel/ConfigStorageTestTrait.php new file mode 100644 index 0000000..088681b --- /dev/null +++ b/tests/src/Kernel/ConfigStorageTestTrait.php @@ -0,0 +1,146 @@ + [filter wrapping [sync]] + * import: + * [filter wrapping [sync]] ----------------------------------------> [active] + * + * Drupal core >=8.8: + * export: + * [active] ---------------> [export transformation] -----------------> [sync] + * import: + * [sync] -----------------> [import transformation] ---------------> [active] + * + * The test for exporting should set up the active store by saving config and + * what is expected to be exported and then compare the export storage with it. + * The test for importing should set up the sync store by writing files (or by + * saving arrays to the FileStorage) and then comparing the expectations with + * the import storage. + * + * Tests using this trait will work with Config Filter 1.x and 2.x and they will + * also work when using the core api directly provided the module has the same + * behaviour with the event listener as with the config filter plugin. + * + * @see \Drupal\Tests\config_filter\Kernel\ExampleStorageKernelTest + */ +trait ConfigStorageTestTrait { + + /** + * Copies configuration objects from source storage to target storage. + * + * This method is defined in a trait used in KernelTestBase. + */ + abstract protected function copyConfig(StorageInterface $source_storage, StorageInterface $target_storage); + + /** + * The active config store, read only. + * + * @return \Drupal\Core\Config\StorageInterface + * The storage with the active config. + */ + protected function getActiveStorage(): StorageInterface { + // Return the active storage in read-only mode. Config should be properly + // saved and the active storage should not be directly manipulated. + return new ReadOnlyStorage($this->container->get('config.storage')); + } + + /** + * The file storage associated with the sync storage. + * + * Use this to manipulate the files in the sync folder. In order to export the + * configuration to the sync storage use the following: + * @code + * $this->copyConfig($this->getExportStorage(), $this->getSyncFileStorage()); + * @endcode + * + * @return \Drupal\Core\Config\StorageInterface + * The storage with the files from the sync folder. + */ + protected function getSyncFileStorage(): StorageInterface { + // We do not return the config.storage.sync service so that we can bypass + // config filter. + return new FileStorage(Settings::get('config_sync_directory')); + } + + /** + * Trigger the export transformation and return its result. + * + * @return \Drupal\Core\Config\StorageInterface + * The storage with the config to be exported. + */ + protected function getExportStorage(): StorageInterface { + $manager = new ExportStorageManager( + $this->container->get('config.storage'), + $this->container->get('database'), + $this->container->get('event_dispatcher'), + // We use the null lock because these tests can anyway not be run in + // parallel. We would have to lock around accessing the storage and + // copying the content to a new memory storage. + new NullLockBackend() + ); + // This is the same essentially as the config.storage.export service + // but the container doesn't cache it so we can access it several times + // with updated config in the same test and trigger the transformation anew. + return $manager->getStorage(); + } + + /** + * Trigger the import transformation and return its result. + * + * @return \Drupal\Core\Config\StorageInterface + * The storage with the config to be imported. + */ + protected function getImportStorage(): StorageInterface { + // Transform the sync storage for import. + return $this->container->get('config.import_transformer')->transform($this->container->get('config.storage.sync')); + } + + /** + * Asserts that two config storage objects have the same content. + * + * @param \Drupal\Core\Config\StorageInterface $expected + * The storage with the expected data. + * @param \Drupal\Core\Config\StorageInterface $actual + * The storage with the actual data. + * @param string $message + * The message to add to the assertion. + */ + protected static function assertStorageEquals(StorageInterface $expected, StorageInterface $actual, string $message = '') { + // The same collections have to exist. + static::assertEqualsCanonicalizing($expected->getAllCollectionNames(), $actual->getAllCollectionNames(), $message); + // Now loop over all collections and assert the data to be equal. + foreach (array_merge([StorageInterface::DEFAULT_COLLECTION], $expected->getAllCollectionNames()) as $collection) { + $expected_collection = $expected->createCollection($collection); + $actual_collection = $actual->createCollection($collection); + // The same names are present in both. + static::assertEqualsCanonicalizing($expected_collection->listAll(), $actual_collection->listAll(), $message); + foreach ($expected_collection->listAll() as $name) { + // The same data can be read from both. + static::assertEquals($expected_collection->read($name), $actual_collection->read($name), $message . ' ' . $name); + } + } + } + +} diff --git a/tests/src/Kernel/ExampleStorageKernelTest.php b/tests/src/Kernel/ExampleStorageKernelTest.php new file mode 100644 index 0000000..bfa4ea5 --- /dev/null +++ b/tests/src/Kernel/ExampleStorageKernelTest.php @@ -0,0 +1,81 @@ +installConfig(['system']); + + // Set the site name and slogan. + $this->config('system.site')->set('name', 'Config Test')->set('slogan', 'Testing is fun!')->save(); + } + + /** + * Example to test export. + */ + public function testExampleExport() { + // Set up expectation for export. + $expectedExport = new MemoryStorage(); + $this->copyConfig($this->getActiveStorage(), $expectedExport); + + // Simulate the filter. + $system_site = $expectedExport->read('system.site'); + // Exporting means triggering the write filter methods. + $system_site['slogan'] = 'Testing is fun! Arrr'; + $expectedExport->write('system.site', $system_site); + + // Do assertions. + static::assertStorageEquals($expectedExport, $this->getExportStorage()); + } + + /** + * Example to test import. + */ + public function testExampleImport() { + // Write active config to file system as is. + // This is not a config export, but for the sake of the test we set up + // the sync storage to contain the same content as the active config. + $this->copyConfig($this->getActiveStorage(), $this->getSyncFileStorage()); + + // Set up expectation for import. + $expectedImport = new MemoryStorage(); + $this->copyConfig($this->getSyncFileStorage(), $expectedImport); + + // Simulate the filter. + $system_site = $expectedImport->read('system.site'); + // Importing means triggering the read filter methods. + $system_site['name'] = 'Config Test Arrr'; + $expectedImport->write('system.site', $system_site); + + // Do assertions. + static::assertStorageEquals($expectedImport, $this->getImportStorage()); + } + +}