From 39a2dc0c41c8cc6f00090663f7f36594bd32810f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 20 Dec 2016 11:17:42 +0100 Subject: [PATCH] add an abstract class for testing ExportInterface This class provide methods to test Exports in a simple way. --- Test/AbstractExportTestHelper.php | 320 ++++++++++++++++++++++++++++++ 1 file changed, 320 insertions(+) create mode 100644 Test/AbstractExportTestHelper.php diff --git a/Test/AbstractExportTestHelper.php b/Test/AbstractExportTestHelper.php new file mode 100644 index 000000000..4cfad3107 --- /dev/null +++ b/Test/AbstractExportTestHelper.php @@ -0,0 +1,320 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +namespace Chill\MainBundle\Test; + +use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; +use Doctrine\ORM\QueryBuilder; +use Doctrine\ORM\NativeQuery; +use Symfony\Component\Security\Core\Role\Role; + +/** + * This class provide a set of tests for exports. + * + * The tests provided by this class will check basic things, like + * the type of value are conform to the expected, etc. + * + * @author julien.fastre@champs-libres.coop + */ +abstract class AbstractExportTestHelper extends KernelTestCase +{ + /** + * Create an instance of the report to test + * + * @return \Chill\MainBundle\Export\ExportInterface an instance of the export to test + */ + public abstract function getExport(); + + /** + * Create possible combinaison of data (produced by the form). + * + * This data will be used to generate data providers using this data. + * + * @return array an array of data. Example : `array( array(), array('fields' => array(1,2,3), ...)` where an empty array and `array(1,2,3)` are possible values + */ + public abstract function getFormData(); + + /** + * get the possible modifiers which could apply in combination to this + * export. + * . + * + * @return array of string[] an array which contains an array of possible modifiers. Example : `array( array('modifier_1', 'modifier_2'), array('modifier_1'), ...)` + */ + abstract public function getModifiersCombination(); + + /** + * Return an array usable as ACL + * + * If this method is overridden, the returned result must be an array + * with this form : + * + * ``` + * array( + * array( + * 'center' => //center instance + * 'circles' => array(// array of circles instances ) + * ) + * ); + * ``` + * + */ + public function getACL() + { + if (static::$kernel === null) { + static::bootKernel(); + } + + $em = static::$kernel->getContainer() + ->get('doctrine.orm.entity_manager'); + + $centers = $em->getRepository('ChillMainBundle:Center') + ->findAll(); + $circles = $em->getRepository('ChillMainBundle:Scope') + ->findAll(); + + if (count($centers) === 0) { + throw new \RuntimeException("No center found. Did you forget to " + . "run `doctrine:fixtures:load` command before ?"); + } + if (count($circles) === 0) { + throw new \RuntimeException("No circle found. Did you forget to " + . "run `doctrine:fixtures:load` command before ?"); + } + + return array([ + 'center' => $centers[0], + 'circles' => [ + $circles + ]]); + } + + /** + * Test that the getType method return a string + */ + public function testGetType() + { + $export = $this->getExport(); + + $this->assertInternalType('string', $export->getType(), + "Assert that the `getType` method return a string"); + $this->assertNotEmpty($export->getType(), "Assert that the `getType` method" + . " does not return an empty string."); + } + + /** + * Test that the description is not empty + */ + public function testGetDescription() + { + $export = $this->getExport(); + + $this->assertInternalType('string', $export->getDescription(), + "Assert that the `getDescription` method return a string"); + $this->assertNotEmpty($export->getDescription(), + "Assert that the `getDescription` method does not return an empty " + . "string."); + } + + /** + * create data for `ìnitiateQuery` method + */ + public function dataProviderInitiateQuery() + { + $acl = $this->getAcl(); + + foreach($this->getModifiersCombination() as $modifiers) { + + foreach($this->getFormData() as $data) { + + yield array($modifiers, $acl, $data); + } + } + } + + public function dataProviderGetQueryKeys() + { + foreach($this->getFormData() as $data) { + yield array($data); + } + } + + /** + * + * test that the query returned is a QueryBuilder or a NativeQuery. + * + * If the query is a QueryBuilder, test that select and from is not empty. + * + * If the query is a native sql, test the query is not empty (length is + * > 0). + * + * @dataProvider dataProviderInitiateQuery + */ + public function testInitiateQuery($modifiers, $acl, $data) + { + $query = $this->getExport()->initiateQuery($modifiers, $acl, $data); + + $this->assertTrue($query instanceof QueryBuilder || $query instanceof NativeQuery, + sprintf("Assert that the returned query is an instance of %s or %s", + QueryBuilder::class, Query::class)); + + if ($query instanceof QueryBuilder) { + + $this->assertGreaterThanOrEqual(1, count($query->getDQLPart('select')), + "assert there is at least one 'select' part"); + + $this->assertGreaterThanOrEqual(1, count($query->getDQLPart('from')), + "assert there is at least one 'from' part"); + + } elseif ($query instanceof NativeQuery) { + $this->assertNotEmpty($query->getSQL(), + "check that the SQL query is not empty"); + } + } + + /** + * Test that supportsModifier return : + * + * - an array of string, if the query is a QueryBuilder ; + * - nothing, if the query is a native SQL + * + * @dataProvider dataProviderInitiateQuery + */ + public function testSupportsModifier($modifiers, $acl, $data) + { + $export = $this->getExport(); + $query = $export->initiateQuery($modifiers, $acl, $data); + + if ($query instanceof QueryBuilder) { + $this->assertContainsOnly('string', $export->supportsModifiers(), + "Test that the `supportsModifiers` method returns only strings"); + } elseif ($query instanceof NativeQuery) { + $this->assertTrue($export->supportsModifiers() === null || + count($export->supportsModifiers()) === 0, + "Test that the `supportsModifier` methods returns null or an empty array"); + } + } + + /** + * Test required role is an instance of Role + */ + public function testRequiredRole() + { + $role = $this->getExport()->requiredRole(); + + $this->assertInstanceOf(Role::class, $role, + sprintf("test that the returned value of `requiredRole` is an instance " + . "of %s", Role::class)); + } + + /** + * Test the formatters type are string + */ + public function testGetAllowedFormattersType() + { + $formattersTypes = $this->getExport()->getAllowedFormattersTypes(); + + $this->assertContainsOnly("string", $formattersTypes, + "Test that the method `getAllowedFormattersTypes` returns an array of string"); + } + + /** + * Test that the query keys are strings + * + * @param array $data + * @dataProvider dataProviderGetQueryKeys + */ + public function testGetQueryKeys(array $data) + { + $queryKeys = $this->getExport()->getQueryKeys($data); + + $this->assertContainsOnly("string", $queryKeys, + "test that the query keys returned by `getQueryKeys` are only strings"); + + } + + /** + * + * Test that + * + * - the results have a correct form (are arrays or traversable) + * - each key in a row are present in getQueryKeys ; + * - each returned object of the `getLabels` method is callable + * - each result can be converted to string using this callable + * - each of this callable can provide a string for '_header' + * + * @param string[] $modifiers + * @param array $acl + * @param array $data + * + * @dataProvider dataProviderInitiateQuery + */ + public function testGetResultsAndLabels($modifiers, $acl, array $data) + { + // it is more convenient to group the `getResult` and `getLabels` test + // due to the fact that testing both methods use the same tools. + + $queryKeys = $this->getExport()->getQueryKeys($data); + $query = $this->getExport()->initiateQuery($modifiers, $acl, $data); + + // limit the result for the query for performance reason (only for QueryBuilder, + // not possible in NativeQuery) + if ($query instanceof QueryBuilder) { + $query->setMaxResults(1); + } + + $results = $this->getExport()->getResult($query, $data); + + $this->assertInternalType('array', $results, + "assert that the returned result is an array"); + + if (count($results) === 0) { + $this->markTestIncomplete("The result is empty. We cannot process tests " + . "on results"); + } + + // testing the result + $result = $results[0]; + + $this->assertTrue( $result instanceof \Traversable || is_array($result), + "test that each row in the result is traversable or an array"); + + foreach ($result as $key => $value) { + $this->assertContains($key, $queryKeys, + "test that each key is present in `getQueryKeys`"); + + $closure = $this->getExport()->getLabels($key, array($value), $data); + + $this->assertTrue(is_callable($closure, false), + "test that the `getLabels` for key is a callable"); + $this->assertTrue(is_string((string) call_user_func($closure, $value)), + sprintf("test that the callable return by `getLabels` for key %s " + . "is a string or an be converted to a string", $key)); + + $this->assertTrue( + // conditions + is_string((string) call_user_func($closure, '_header')) + && !empty(call_user_func($closure, '_header')) + && call_user_func($closure, '_header') !== '_header', + // message + sprintf("Test that the callable return by `getLabels` for key %s " + . "is a string and not empty", $key) + ); + } + + } +}