diff --git a/composer.json b/composer.json index 71dcc9605c..24e405d9bd 100644 --- a/composer.json +++ b/composer.json @@ -50,10 +50,12 @@ "scripts": { "pre-autoload-dump": "Drupal\\Core\\Composer\\Composer::preAutoloadDump", "post-autoload-dump": [ - "Drupal\\Core\\Composer\\Composer::ensureHtaccess" + "Drupal\\Core\\Composer\\Composer::ensureHtaccess", + "Drupal\\Core\\Composer\\Composer::upgradePHPUnit" ], "post-package-install": "Drupal\\Core\\Composer\\Composer::vendorTestCodeCleanup", "post-package-update": "Drupal\\Core\\Composer\\Composer::vendorTestCodeCleanup", + "drupal-phpunit-upgrade": "@composer upgrade phpunit/phpunit --with-dependencies", "phpcs": "phpcs --standard=core/phpcs.xml.dist --runtime-set installed_paths $($COMPOSER_BINARY config vendor-dir)/drupal/coder/coder_sniffer --", "phpcbf": "phpcbf --standard=core/phpcs.xml.dist --runtime-set installed_paths $($COMPOSER_BINARY config vendor-dir)/drupal/coder/coder_sniffer --" }, diff --git a/core/composer.json b/core/composer.json index 31bc51a0fa..c4bb384b92 100644 --- a/core/composer.json +++ b/core/composer.json @@ -44,7 +44,7 @@ "jcalderonzumba/gastonjs": "^1.0.2", "jcalderonzumba/mink-phantomjs-driver": "^0.3.1", "mikey179/vfsStream": "^1.2", - "phpunit/phpunit": ">=4.8.35 <5", + "phpunit/phpunit": "^4.8.35 || ^6.1", "phpspec/prophecy": "^1.4", "symfony/css-selector": "~3.2.8", "symfony/phpunit-bridge": "^3.4.0@beta" diff --git a/core/lib/Drupal/Core/Composer/Composer.php b/core/lib/Drupal/Core/Composer/Composer.php index 016f93a348..8bfc7bb8c2 100644 --- a/core/lib/Drupal/Core/Composer/Composer.php +++ b/core/lib/Drupal/Core/Composer/Composer.php @@ -6,6 +6,7 @@ use Composer\Script\Event; use Composer\Installer\PackageEvent; use Composer\Semver\Constraint\Constraint; +use PHPUnit\Runner\Version; /** * Provides static functions for composer script events. @@ -143,6 +144,24 @@ public static function ensureHtaccess(Event $event) { } } + /** + * Fires the drupal-phpunit-upgrade script event if necessary. + * + * @param \Composer\Script\Event $event + */ + public static function upgradePHPUnit(Event $event) { + if (class_exists('PHPUnit_Runner_Version')) { + $phpunit_version = \PHPUnit_Runner_Version::id(); + } + if (class_exists('\PHPUnit\Runner\Version')) { + $phpunit_version = Version::id(); + } + // @todo if we haven't determine version throw an error. + if (version_compare(PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION, '7.1') >= 0 && version_compare($phpunit_version, '6.0') < 0) { + $event->getComposer()->getEventDispatcher()->dispatchScript('drupal-phpunit-upgrade'); + } + } + /** * Remove possibly problematic test files from vendored projects. * diff --git a/core/modules/simpletest/src/WebTestBase.php b/core/modules/simpletest/src/WebTestBase.php index 4b6f421e55..441b2398f4 100644 --- a/core/modules/simpletest/src/WebTestBase.php +++ b/core/modules/simpletest/src/WebTestBase.php @@ -18,7 +18,7 @@ use Drupal\system\Tests\Cache\AssertPageCacheContextsAndTagsTrait; use Drupal\Tests\EntityViewTrait; use Drupal\Tests\block\Traits\BlockCreationTrait as BaseBlockCreationTrait; -use Drupal\Tests\Listeners\DeprecationListener; +use Drupal\Tests\Listeners\DeprecationListenerTrait; use Drupal\Tests\node\Traits\ContentTypeCreationTrait; use Drupal\Tests\node\Traits\NodeCreationTrait; use Drupal\Tests\Traits\Core\CronRunTrait; @@ -698,7 +698,7 @@ protected function curlHeaderCallback($curlHandler, $header) { if ($parameters[1] === 'User deprecated function') { if (getenv('SYMFONY_DEPRECATIONS_HELPER') !== 'disabled') { $message = (string) $parameters[0]; - if (!in_array($message, DeprecationListener::getSkippedDeprecations())) { + if (!in_array($message, DeprecationListenerTrait::getSkippedDeprecations())) { call_user_func_array([&$this, 'error'], $parameters); } } diff --git a/core/modules/tour/src/Tests/TourTestBase.php b/core/modules/tour/src/Tests/TourTestBase.php index e139650706..f551c8151b 100644 --- a/core/modules/tour/src/Tests/TourTestBase.php +++ b/core/modules/tour/src/Tests/TourTestBase.php @@ -14,6 +14,17 @@ */ abstract class TourTestBase extends WebTestBase { + /** + * {@inheritdoc} + */ + protected function setUp() { + // Support both PHPUnit 4 and 6. + if (!class_exists('\PHPUnit_Util_XML') && class_exists('\PHPUnit\Util\XML')) { + class_alias('\PHPUnit\Util\XML', '\PHPUnit_Util_XML'); + } + parent::setUp(); + } + /** * Assert function to determine if tips rendered to the page * have a corresponding page element. diff --git a/core/modules/update/tests/src/Unit/UpdateFetcherTest.php b/core/modules/update/tests/src/Unit/UpdateFetcherTest.php index c3e447d1fc..61c767deda 100644 --- a/core/modules/update/tests/src/Unit/UpdateFetcherTest.php +++ b/core/modules/update/tests/src/Unit/UpdateFetcherTest.php @@ -28,7 +28,7 @@ class UpdateFetcherTest extends UnitTestCase { */ protected function setUp() { $config_factory = $this->getConfigFactoryStub(['update.settings' => ['fetch_url' => 'http://www.example.com']]); - $http_client_mock = $this->getMock('\GuzzleHttp\ClientInterface'); + $http_client_mock = $this->createMock('\GuzzleHttp\ClientInterface'); $this->updateFetcher = new UpdateFetcher($config_factory, $http_client_mock); } diff --git a/core/phpunit.xml.dist b/core/phpunit.xml.dist index bfe74673b2..750c6e2b50 100644 --- a/core/phpunit.xml.dist +++ b/core/phpunit.xml.dist @@ -49,16 +49,11 @@ - + - + - - - - diff --git a/core/tests/Drupal/KernelTests/KernelTestBase.php b/core/tests/Drupal/KernelTests/KernelTestBase.php index 32c97b63b5..91da81edf8 100644 --- a/core/tests/Drupal/KernelTests/KernelTestBase.php +++ b/core/tests/Drupal/KernelTests/KernelTestBase.php @@ -20,6 +20,7 @@ use Drupal\simpletest\AssertContentTrait; use Drupal\Tests\AssertHelperTrait; use Drupal\Tests\ConfigTestTrait; +use Drupal\Tests\Phpunit5FCTrait; use Drupal\Tests\RandomGeneratorTrait; use Drupal\Tests\TestRequirementsTrait; use Drupal\simpletest\TestServiceProvider; @@ -76,6 +77,7 @@ use RandomGeneratorTrait; use ConfigTestTrait; use TestRequirementsTrait; + use Phpunit5FCTrait; /** * {@inheritdoc} diff --git a/core/tests/Drupal/Tests/Component/Diff/Engine/DiffOpTest.php b/core/tests/Drupal/Tests/Component/Diff/Engine/DiffOpTest.php index 1a649ae510..dbbb6ec081 100644 --- a/core/tests/Drupal/Tests/Component/Diff/Engine/DiffOpTest.php +++ b/core/tests/Drupal/Tests/Component/Diff/Engine/DiffOpTest.php @@ -4,6 +4,7 @@ use Drupal\Component\Diff\Engine\DiffOp; use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\Error\Error; /** * Test DiffOp base class. @@ -24,7 +25,12 @@ class DiffOpTest extends TestCase { * @covers ::reverse */ public function testReverse() { - $this->setExpectedException(\PHPUnit_Framework_Error::class); + if (method_exists($this, 'expectException')) { + $this->expectException(Error::class); + } + else { + $this->setExpectedException(\PHPUnit_Framework_Error::class); + } $op = new DiffOp(); $result = $op->reverse(); } diff --git a/core/tests/Drupal/Tests/Component/PhpStorage/FileStorageTest.php b/core/tests/Drupal/Tests/Component/PhpStorage/FileStorageTest.php index 52e1b83c03..168d1603e3 100644 --- a/core/tests/Drupal/Tests/Component/PhpStorage/FileStorageTest.php +++ b/core/tests/Drupal/Tests/Component/PhpStorage/FileStorageTest.php @@ -5,7 +5,7 @@ use Drupal\Component\PhpStorage\FileStorage; use Drupal\Component\Utility\Random; use org\bovigo\vfs\vfsStreamDirectory; -use PHPUnit_Framework_Error_Warning; +use PHPUnit\Framework\Error\Warning; /** * @coversDefaultClass \Drupal\Component\PhpStorage\FileStorage @@ -99,7 +99,13 @@ public function testCreateDirectoryFailWarning() { 'bin' => 'test', ]); $code = "setExpectedException(PHPUnit_Framework_Error_Warning::class, 'mkdir(): Permission Denied'); + if (method_exists($this, 'expectException')) { + $this->expectException(Warning::class); + $this->expectExceptionMessage('mkdir(): Permission Denied'); + } + else { + $this->setExpectedException(\PHPUnit_Framework_Error_Warning::class, 'mkdir(): Permission Denied'); + } $storage->save('subdirectory/foo.php', $code); } diff --git a/core/tests/Drupal/Tests/Listeners/DeprecationListener.php b/core/tests/Drupal/Tests/Listeners/DeprecationListenerTrait.php similarity index 97% rename from core/tests/Drupal/Tests/Listeners/DeprecationListener.php rename to core/tests/Drupal/Tests/Listeners/DeprecationListenerTrait.php index 80b3d31c97..8b828e0178 100644 --- a/core/tests/Drupal/Tests/Listeners/DeprecationListener.php +++ b/core/tests/Drupal/Tests/Listeners/DeprecationListenerTrait.php @@ -9,12 +9,18 @@ * This class will be removed once all the deprecation notices have been * fixed. */ -class DeprecationListener extends \PHPUnit_Framework_BaseTestListener { +trait DeprecationListenerTrait { /** - * {@inheritdoc} + * Reacts to the end of a test. + * + * @param $test + * The test object that has ended its test run. + * @param float $time + * The time the test took. */ - public function endTest(\PHPUnit_Framework_Test $test, $time) { + protected function deprecationEndTest($test, $time) { + /** @var \PHPUnit\Framework\Test $test */ // Need to edit the file of deprecations. if ($file = getenv('SYMFONY_DEPRECATIONS_SERIALIZE')) { $deprecations = file_get_contents($file); diff --git a/core/tests/Drupal/Tests/Listeners/DrupalComponentTestListener.php b/core/tests/Drupal/Tests/Listeners/DrupalComponentTestListenerTrait.php similarity index 52% rename from core/tests/Drupal/Tests/Listeners/DrupalComponentTestListener.php rename to core/tests/Drupal/Tests/Listeners/DrupalComponentTestListenerTrait.php index c349677305..0284f6da90 100644 --- a/core/tests/Drupal/Tests/Listeners/DrupalComponentTestListener.php +++ b/core/tests/Drupal/Tests/Listeners/DrupalComponentTestListenerTrait.php @@ -5,20 +5,28 @@ use Drupal\KernelTests\KernelTestBase;; use Drupal\Tests\BrowserTestBase;; use Drupal\Tests\UnitTestCase; -use PHPUnit\Framework\BaseTestListener; +use PHPUnit\Framework\AssertionFailedError; /** * Ensures that no component tests are extending a core test base class. + * + * @internal */ -class DrupalComponentTestListener extends BaseTestListener { +trait DrupalComponentTestListenerTrait { /** - * {@inheritdoc} + * Reacts to the end of a test. + * + * @param $test + * The test object that has ended its test run. + * @param float $time + * The time the test took. */ - public function endTest(\PHPUnit_Framework_Test $test, $time) { + protected function componentEndTest($test, $time) { + /** @var \PHPUnit\Framework\Test $test */ if (substr($test->toString(), 0, 22) == 'Drupal\Tests\Component') { if ($test instanceof BrowserTestBase || $test instanceof KernelTestBase || $test instanceof UnitTestCase) { - $error = new \PHPUnit_Framework_AssertionFailedError('Component tests should not extend a core test base class.'); + $error = new AssertionFailedError('Component tests should not extend a core test base class.'); $test->getTestResultObject()->addFailure($test, $error, $time); } } diff --git a/core/tests/Drupal/Tests/Listeners/DrupalListener.php b/core/tests/Drupal/Tests/Listeners/DrupalListener.php new file mode 100644 index 0000000000..db0c822588 --- /dev/null +++ b/core/tests/Drupal/Tests/Listeners/DrupalListener.php @@ -0,0 +1,36 @@ +deprecationEndTest($test, $time); + $this->componentEndTest($test, $time); + $this->standardsEndTest($test, $time); + } + + } +} diff --git a/core/tests/Drupal/Tests/Listeners/DrupalStandardsListener.php b/core/tests/Drupal/Tests/Listeners/DrupalStandardsListenerTrait.php similarity index 80% rename from core/tests/Drupal/Tests/Listeners/DrupalStandardsListener.php rename to core/tests/Drupal/Tests/Listeners/DrupalStandardsListenerTrait.php index fd15f8218b..d94299cc9f 100644 --- a/core/tests/Drupal/Tests/Listeners/DrupalStandardsListener.php +++ b/core/tests/Drupal/Tests/Listeners/DrupalStandardsListenerTrait.php @@ -2,15 +2,18 @@ namespace Drupal\Tests\Listeners; -use PHPUnit\Framework\BaseTestListener; +use PHPUnit\Framework\AssertionFailedError; use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\TestSuite; /** * Listens for PHPUnit tests and fails those with invalid coverage annotations. * * Enforces various coding standards within test runs. + * + * @internal */ -class DrupalStandardsListener extends BaseTestListener { +trait DrupalStandardsListenerTrait { /** * Signals a coding standards failure to the user. @@ -21,10 +24,10 @@ class DrupalStandardsListener extends BaseTestListener { * The message to add to the failure notice. The test class name and test * name will be appended to this message automatically. */ - protected function fail(TestCase $test, $message) { + private function fail(TestCase $test, $message) { // Add the report to the test's results. $message .= ': ' . get_class($test) . '::' . $test->getName(); - $fail = new \PHPUnit_Framework_AssertionFailedError($message); + $fail = new AssertionFailedError($message); $result = $test->getTestResultObject(); $result->addFailure($test, $fail, 0); } @@ -38,7 +41,7 @@ protected function fail(TestCase $test, $message) { * @return bool * TRUE if the class exists, FALSE otherwise. */ - protected function classExists($class) { + private function classExists($class) { return class_exists($class, TRUE) || trait_exists($class, TRUE) || interface_exists($class, TRUE); } @@ -50,7 +53,7 @@ protected function classExists($class) { * @param \PHPUnit\Framework\TestCase $test * The test to examine. */ - public function checkValidCoversForTest(TestCase $test) { + private function checkValidCoversForTest(TestCase $test) { // If we're generating a coverage report already, don't do anything here. if ($test->getTestResultObject() && $test->getTestResultObject()->getCollectCodeCoverageInformation()) { return; @@ -141,7 +144,7 @@ public function checkValidCoversForTest(TestCase $test) { } /** - * {@inheritdoc} + * Reacts to the end of a test. * * We must mark this method as belonging to the special legacy group because * it might trigger an E_USER_DEPRECATED error during coverage annotation @@ -151,22 +154,58 @@ public function checkValidCoversForTest(TestCase $test) { * * @group legacy * + * @param $test + * The test object that has ended its test run. + * @param float $time + * The time the test took. + * * @see http://symfony.com/doc/current/components/phpunit_bridge.html#mark-tests-as-legacy */ - public function endTest(\PHPUnit_Framework_Test $test, $time) { + private function doEndTest($test, $time) { // \PHPUnit_Framework_Test does not have any useful methods of its own for // our purpose, so we have to distinguish between the different known // subclasses. if ($test instanceof TestCase) { $this->checkValidCoversForTest($test); } - elseif ($test instanceof \PHPUnit_Framework_TestSuite) { + elseif ($this->isTestSuite($test)) { foreach ($test->getGroupDetails() as $tests) { foreach ($tests as $test) { - $this->endTest($test, $time); + $this->doEndTest($test, $time); } } } } + /** + * Determine if a test object is a test suite regardless of PHPUnit version. + * + * @param $test + * The test object to test if it is a test suite. + * + * @return bool + * TRUE if it is a test suite, FALSE if not. + */ + private function isTestSuite($test) { + if (class_exists('\PHPUnit_Framework_TestSuite') && $test instanceof \PHPUnit_Framework_TestSuite) { + return TRUE; + } + if (class_exists('PHPUnit\Framework\TestSuite') && $test instanceof TestSuite) { + return TRUE; + } + return FALSE; + } + + /** + * Reacts to the end of a test. + * + * @param $test + * The test object that has ended its test run. + * @param float $time + * The time the test took. + */ + protected function standardsEndTest($test, $time) { + $this->doEndTest($test, $time); + } + } diff --git a/core/tests/Drupal/Tests/Listeners/HtmlOutputPrinter.php b/core/tests/Drupal/Tests/Listeners/HtmlOutputPrinter.php index ac22072d16..fb1e4f4765 100644 --- a/core/tests/Drupal/Tests/Listeners/HtmlOutputPrinter.php +++ b/core/tests/Drupal/Tests/Listeners/HtmlOutputPrinter.php @@ -2,70 +2,39 @@ namespace Drupal\Tests\Listeners; -/** - * Defines a class for providing html output results for functional tests. - */ -class HtmlOutputPrinter extends \PHPUnit_TextUI_ResultPrinter { - - /** - * File to write html links to. - * - * @var string - */ - protected $browserOutputFile; - +use PHPUnit\Framework\TestResult; +use PHPUnit\TextUI\ResultPrinter; + +if (class_exists('PHPUnit_Runner_Version') && version_compare(\PHPUnit_Runner_Version::id(), '6.0.0', '<')) { + class_alias('Drupal\Tests\Listeners\Legacy\HtmlOutputPrinter', 'Drupal\Tests\Listeners\HtmlOutputPrinter'); + // Using an early return instead of a else does not work when using the + // PHPUnit phar due to some weird PHP behavior (the class gets defined without + // executing the code before it and so the definition is not properly + // conditional). +} +else { /** - * {@inheritdoc} + * Defines a class for providing html output results for functional tests. */ - public function __construct($out, $verbose, $colors, $debug, $numberOfColumns) { - parent::__construct($out, $verbose, $colors, $debug, $numberOfColumns); - if ($html_output_directory = getenv('BROWSERTEST_OUTPUT_DIRECTORY')) { - // Initialize html output debugging. - $html_output_directory = rtrim($html_output_directory, '/'); - - // Check if directory exists. - if (!is_dir($html_output_directory) || !is_writable($html_output_directory)) { - $this->writeWithColor('bg-red, fg-black', "HTML output directory $html_output_directory is not a writable directory."); - } - else { - // Convert to a canonicalized absolute pathname just in case the current - // working directory is changed. - $html_output_directory = realpath($html_output_directory); - $this->browserOutputFile = tempnam($html_output_directory, 'browser_output_'); - if ($this->browserOutputFile) { - touch($this->browserOutputFile); - } - else { - $this->writeWithColor('bg-red, fg-black', "Unable to create a temporary file in $html_output_directory."); - } - } - } - - if ($this->browserOutputFile) { - putenv('BROWSERTEST_OUTPUT_FILE=' . $this->browserOutputFile); - } - else { - // Remove any environment variable. - putenv('BROWSERTEST_OUTPUT_FILE'); + class HtmlOutputPrinter extends ResultPrinter { + use HtmlOutputPrinterTrait; + /** + * {@inheritdoc} + */ + public function __construct($out = NULL, $verbose = FALSE, $colors = self::COLOR_DEFAULT, $debug = FALSE, $numberOfColumns = 80, $reverse = FALSE) { + parent::__construct($out, $verbose, $colors, $debug, $numberOfColumns, $reverse); + + $this->setUpHtmlOutput(); } - } - /** - * {@inheritdoc} - */ - public function printResult(\PHPUnit_Framework_TestResult $result) { - parent::printResult($result); + /** + * {@inheritdoc} + */ + public function printResult(TestResult $result) { + parent::printResult($result); - if ($this->browserOutputFile) { - $contents = file_get_contents($this->browserOutputFile); - if ($contents) { - $this->writeNewLine(); - $this->writeWithColor('bg-yellow, fg-black', 'HTML output was generated'); - $this->write($contents); - } - // No need to keep the file around any more. - unlink($this->browserOutputFile); + $this->printHtmlOutput(); } - } + } } diff --git a/core/tests/Drupal/Tests/Listeners/HtmlOutputPrinter.php b/core/tests/Drupal/Tests/Listeners/HtmlOutputPrinterTrait.php similarity index 85% copy from core/tests/Drupal/Tests/Listeners/HtmlOutputPrinter.php copy to core/tests/Drupal/Tests/Listeners/HtmlOutputPrinterTrait.php index ac22072d16..0a9cc42dc6 100644 --- a/core/tests/Drupal/Tests/Listeners/HtmlOutputPrinter.php +++ b/core/tests/Drupal/Tests/Listeners/HtmlOutputPrinterTrait.php @@ -5,7 +5,7 @@ /** * Defines a class for providing html output results for functional tests. */ -class HtmlOutputPrinter extends \PHPUnit_TextUI_ResultPrinter { +trait HtmlOutputPrinterTrait { /** * File to write html links to. @@ -17,8 +17,7 @@ class HtmlOutputPrinter extends \PHPUnit_TextUI_ResultPrinter { /** * {@inheritdoc} */ - public function __construct($out, $verbose, $colors, $debug, $numberOfColumns) { - parent::__construct($out, $verbose, $colors, $debug, $numberOfColumns); + protected function setUpHtmlOutput() { if ($html_output_directory = getenv('BROWSERTEST_OUTPUT_DIRECTORY')) { // Initialize html output debugging. $html_output_directory = rtrim($html_output_directory, '/'); @@ -53,9 +52,7 @@ public function __construct($out, $verbose, $colors, $debug, $numberOfColumns) { /** * {@inheritdoc} */ - public function printResult(\PHPUnit_Framework_TestResult $result) { - parent::printResult($result); - + protected function printHtmlOutput() { if ($this->browserOutputFile) { $contents = file_get_contents($this->browserOutputFile); if ($contents) { diff --git a/core/tests/Drupal/Tests/Listeners/Legacy/DrupalListener.php b/core/tests/Drupal/Tests/Listeners/Legacy/DrupalListener.php new file mode 100644 index 0000000000..f7c2c76668 --- /dev/null +++ b/core/tests/Drupal/Tests/Listeners/Legacy/DrupalListener.php @@ -0,0 +1,29 @@ +deprecationEndTest($test, $time); + $this->componentEndTest($test, $time); + $this->standardsEndTest($test, $time); + } + +} diff --git a/core/tests/Drupal/Tests/Listeners/Legacy/HtmlOutputPrinter.php b/core/tests/Drupal/Tests/Listeners/Legacy/HtmlOutputPrinter.php new file mode 100644 index 0000000000..e5abb6ffc6 --- /dev/null +++ b/core/tests/Drupal/Tests/Listeners/Legacy/HtmlOutputPrinter.php @@ -0,0 +1,31 @@ +setUpHtmlOutput(); + } + + /** + * {@inheritdoc} + */ + public function printResult(\PHPUnit_Framework_TestResult $result) { + parent::printResult($result); + + $this->printHtmlOutput(); + } + +} diff --git a/core/tests/Drupal/Tests/Phpunit5FCTrait.php b/core/tests/Drupal/Tests/Phpunit5FCTrait.php new file mode 100644 index 0000000000..9a123ada13 --- /dev/null +++ b/core/tests/Drupal/Tests/Phpunit5FCTrait.php @@ -0,0 +1,134 @@ +createMock($originalClassName, $methods, $arguments, $mockClassName, $callOriginalConstructor, $callOriginalClone, $callAutoload, $cloneArguments, $callOriginalMethods, $proxyTarget); + } + + /** + * Returns a mock object for the specified class using the available method. + * + * The getMock method is deprecated in PHPUnit 5 but the createMock method is + * not available in PHPUnit 4. To provide forward compatibility this trait + * provides the createMock method and uses createMock if this method is + * available on the parent class or falls back to getMock if it isn't. + * + * @param string $originalClassName + * Name of the class to mock. + * @param array|null $methods + * When provided, only methods whose names are in the array are replaced + * with a configurable test double. The behavior of the other methods is not + * changed. Providing null means that no methods will be replaced. + * @param array $arguments + * Parameters to pass to the original class' constructor. + * @param string $mockClassName + * Class name for the generated test double class. + * @param bool $callOriginalConstructor + * Can be used to disable the call to the original class' constructor. + * @param bool $callOriginalClone + * Can be used to disable the call to the original class' clone constructor. + * @param bool $callAutoload + * Can be used to disable __autoload() during the generation of the test + * double class. + * @param bool $cloneArguments + * @param bool $callOriginalMethods + * @param object $proxyTarget + * + * @see \PHPUnit_Framework_TestCase::getMock + * + * @return \PHPUnit_Framework_MockObject_MockObject + */ + public function createMock($originalClassName, $methods = array(), array $arguments = array(), $mockClassName = '', $callOriginalConstructor = TRUE, $callOriginalClone = TRUE, $callAutoload = TRUE, $cloneArguments = FALSE, $callOriginalMethods = FALSE, $proxyTarget = NULL) { + if ($this->isPhpunit5()) { + return parent::createMock($originalClassName, $methods, $arguments, $mockClassName, $callOriginalConstructor, $callOriginalClone, $callAutoload, $cloneArguments, $callOriginalMethods, $proxyTarget); + } + return parent::getMock($originalClassName, $methods, $arguments, $mockClassName, $callOriginalConstructor, $callOriginalClone, $callAutoload, $cloneArguments, $callOriginalMethods, $proxyTarget); + } + + /** + * Checks if the trait is used in a class extending the PHPUnit 5 TestCase. + * + * @return bool + */ + private function isPhpunit5() { + // Get the parent class of the currently running test class. + $parent = get_parent_class($this); + // Ensure that the method_exists() check on PHPUnit 5's createMock method is + // carried out on the first parent of $this that does not have access to + // this trait's methods. This is because the trait also has a method called + // createMock(). Most often the check will be made on + // \PHPUnit\Framework\TestCase. + while (method_exists($parent, 'isPhpunit5')) { + $parent = get_parent_class($parent); + } + return method_exists($parent, 'createMock'); + } + + /** + * Compatibility layer for PHPUnit 6 to support PHPUnit 4 code. + * + * @param mixed $class + * @param string $message + * @param int $exception_code + */ + public function setExpectedException($class, $message = '', $exception_code = NULL) { + if (method_exists($this, 'expectException')) { + $this->expectException($class); + if (!empty($message)) { + $this->expectExceptionMessage($message); + } + if ($exception_code !== NULL) { + $this->expectExceptionCode($exception_code); + } + } + else { + parent::setExpectedException($class, $message, $exception_code); + } + } + +} diff --git a/core/tests/Drupal/Tests/Phpunit5FCTraitTest.php b/core/tests/Drupal/Tests/Phpunit5FCTraitTest.php new file mode 100644 index 0000000000..621003f6b3 --- /dev/null +++ b/core/tests/Drupal/Tests/Phpunit5FCTraitTest.php @@ -0,0 +1,98 @@ +assertSame($expected, $class->getMock($this->randomMachineName())); + } + + /** + * Test that createMock is available and calls the correct parent method. + * + * @covers ::createMock + * @dataProvider providerMockVersions + */ + public function testCreateMock($className, $expected) { + $class = new $className(); + $this->assertSame($expected, $class->createMock($this->randomMachineName())); + } + + /** + * Return the class names and the string they return. + * + * @return array + */ + public function providerMockVersions() { + return [ + [UnitTestCasePhpunit4TestClass::class, 'PHPUnit 4'], + [UnitTestCasePhpunit4TestClassExtends::class, 'PHPUnit 4'], + [UnitTestCasePhpunit5TestClass::class, 'PHPUnit 5'], + [UnitTestCasePhpunit5TestClassExtends::class, 'PHPUnit 5'], + ]; + } + +} + +/** + * Test class for \PHPUnit\Framework\TestCase in PHPUnit 4. + */ +class Phpunit4TestClass { + public function getMock($originalClassName) { + return 'PHPUnit 4'; + } + +} + +/** + * Test class for \PHPUnit\Framework\TestCase in PHPUnit 5. + */ +class Phpunit5TestClass { + public function createMock($originalClassName) { + return 'PHPUnit 5'; + } + +} + +/** + * Test class for \Drupal\Tests\UnitTestCase with PHPUnit 4. + */ +class UnitTestCasePhpunit4TestClass extends Phpunit4TestClass { + use Phpunit5FCTrait; + +} + +/** + * Test class for \Drupal\Tests\UnitTestCase with PHPUnit 4. + */ +class UnitTestCasePhpunit4TestClassExtends extends UnitTestCasePhpunit4TestClass { +} + +/** + * Test class for \Drupal\Tests\UnitTestCase with PHPUnit 5. + */ +class UnitTestCasePhpunit5TestClass extends Phpunit5TestClass { + use Phpunit5FCTrait; + +} + +/** + * Test class for \Drupal\Tests\UnitTestCase with PHPUnit 5. + */ +class UnitTestCasePhpunit5TestClassExtends extends UnitTestCasePhpunit5TestClass { +} diff --git a/core/tests/Drupal/Tests/UnitTestCase.php b/core/tests/Drupal/Tests/UnitTestCase.php index 24ad6801bf..b4ac0c3805 100644 --- a/core/tests/Drupal/Tests/UnitTestCase.php +++ b/core/tests/Drupal/Tests/UnitTestCase.php @@ -17,6 +17,8 @@ */ abstract class UnitTestCase extends TestCase { + use Phpunit5FCTrait; + /** * The random generator. * @@ -135,7 +137,7 @@ public function getConfigFactoryStub(array $configs = []) { } // Construct a config factory with the array of configuration object stubs // as its return map. - $config_factory = $this->getMock('Drupal\Core\Config\ConfigFactoryInterface'); + $config_factory = $this->createMock('Drupal\Core\Config\ConfigFactoryInterface'); $config_factory->expects($this->any()) ->method('get') ->will($this->returnValueMap($config_get_map)); @@ -157,7 +159,7 @@ public function getConfigFactoryStub(array $configs = []) { * A mocked config storage. */ public function getConfigStorageStub(array $configs) { - $config_storage = $this->getMock('Drupal\Core\Config\NullStorage'); + $config_storage = $this->createMock('Drupal\Core\Config\NullStorage'); $config_storage->expects($this->any()) ->method('listAll') ->will($this->returnValue(array_keys($configs))); @@ -211,7 +213,7 @@ protected function getBlockMockWithMachineName($machine_name) { * A mock translation object. */ public function getStringTranslationStub() { - $translation = $this->getMock('Drupal\Core\StringTranslation\TranslationInterface'); + $translation = $this->createMock('Drupal\Core\StringTranslation\TranslationInterface'); $translation->expects($this->any()) ->method('translate') ->willReturnCallback(function ($string, array $args = [], array $options = []) use ($translation) { @@ -241,7 +243,7 @@ public function getStringTranslationStub() { * The container with the cache tags invalidator service. */ protected function getContainerWithCacheTagsInvalidator(CacheTagsInvalidatorInterface $cache_tags_validator) { - $container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface'); + $container = $this->createMock('Symfony\Component\DependencyInjection\ContainerInterface'); $container->expects($this->any()) ->method('get') ->with('cache_tags.invalidator') @@ -258,7 +260,7 @@ protected function getContainerWithCacheTagsInvalidator(CacheTagsInvalidatorInte * The class resolver stub. */ protected function getClassResolverStub() { - $class_resolver = $this->getMock('Drupal\Core\DependencyInjection\ClassResolverInterface'); + $class_resolver = $this->createMock('Drupal\Core\DependencyInjection\ClassResolverInterface'); $class_resolver->expects($this->any()) ->method('getInstanceFromDefinition') ->will($this->returnCallback(function ($class) { diff --git a/core/tests/bootstrap.php b/core/tests/bootstrap.php index f78b69ff04..f2edbd2d97 100644 --- a/core/tests/bootstrap.php +++ b/core/tests/bootstrap.php @@ -166,3 +166,38 @@ function drupal_phpunit_populate_class_loader() { // make PHP 5 and 7 handle assertion failures the same way, but this call does // not turn runtime assertions on if they weren't on already. Handle::register(); + +// @todo Decide if we need put in some code for WebTestBase too since there are +// tests using PHPUnit_Util_XML for example. +// PHPUnit 4 to PHPUnit 6 bridge. Tests written for PHPUnit 4 need to work on +// PHPUnit 6 with a minimum of fuss. +if (!class_exists('\PHPUnit_Framework_AssertionFailedError') && class_exists('\PHPUnit\Framework\AssertionFailedError')) { + class_alias('\PHPUnit\Framework\AssertionFailedError', '\PHPUnit_Framework_AssertionFailedError'); +} +if (!class_exists('\PHPUnit_Framework_Error_Warning') && class_exists('\PHPUnit\Framework\Error\Warning')) { + class_alias('\PHPUnit\Framework\Error\Warning', '\PHPUnit_Framework_Error_Warning'); +} +if (!class_exists('\PHPUnit_Framework_Error') && class_exists('\PHPUnit\Framework\Error\Error')) { + class_alias('\PHPUnit\Framework\Error\Error', '\PHPUnit_Framework_Error'); +} +if (!class_exists('\PHPUnit_Util_Test') && class_exists('\PHPUnit\Util\Test')) { + class_alias('\PHPUnit\Util\Test', '\PHPUnit_Util_Test'); +} +if (!class_exists('\PHPUnit_Util_XML') && class_exists('\PHPUnit\Util\XML')) { + class_alias('\PHPUnit\Util\XML', '\PHPUnit_Util_XML'); +} +if (!class_exists('\PHPUnit_Framework_SkippedTestError') && class_exists('\PHPUnit\Framework\SkippedTestError')) { + class_alias('\PHPUnit\Framework\SkippedTestError', '\PHPUnit_Framework_SkippedTestError'); +} +if (!class_exists('\PHPUnit_Framework_Exception') && class_exists('\PHPUnit\Framework\Exception')) { + class_alias('\PHPUnit\Framework\Exception', '\PHPUnit_Framework_Exception'); +} +if (!class_exists('\PHPUnit_Framework_ExpectationFailedException') && class_exists('\PHPUnit\Framework\ExpectationFailedException')) { + class_alias('\PHPUnit\Framework\ExpectationFailedException', '\PHPUnit_Framework_ExpectationFailedException'); +} +if (!class_exists('\PHPUnit_Framework_Constraint_Count') && class_exists('\PHPUnit\Framework\Constraint\Count')) { + class_alias('\PHPUnit\Framework\Constraint\Count', '\PHPUnit_Framework_Constraint_Count'); +} +if (!class_exists('\PHPUnit_Framework_MockObject_Matcher_InvokedRecorder') && class_exists('\PHPUnit\Framework\MockObject\Matcher\InvokedRecorder')) { + class_alias('\PHPUnit\Framework\MockObject\Matcher\InvokedRecorder', '\PHPUnit_Framework_MockObject_Matcher_InvokedRecorder'); +}