diff --git a/.github/workflows/build-changelog.yml b/.github/workflows/build-changelog.yml
index cd0be51e..65b91518 100644
--- a/.github/workflows/build-changelog.yml
+++ b/.github/workflows/build-changelog.yml
@@ -6,10 +6,11 @@ on:
- develop
jobs:
- update_release_draft:
+ update:
+ name: Update
runs-on: ubuntu-latest
steps:
- - name: Run Release Drafter
+ - name: Run
uses: release-drafter/release-drafter@v5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/test-unit.yml b/.github/workflows/test-unit.yml
index 69f92be8..68f7d6fa 100644
--- a/.github/workflows/test-unit.yml
+++ b/.github/workflows/test-unit.yml
@@ -46,7 +46,7 @@ jobs:
- name: Install PHP dependencies
run: |
- if [ "${{ matrix.type }}" != "Phpunit" ] && [ "${{ matrix.type }}" != "StaticAnalysis" ]; then composer remove --no-interaction --no-update phpunit/phpunit johnkary/phpunit-speedtrap --dev; fi
+ if [ "${{ matrix.type }}" != "Phpunit" ] && [ "${{ matrix.type }}" != "StaticAnalysis" ]; then composer remove --no-interaction --no-update phpunit/phpunit atk4/ergebnis-phpunit-slow-test-detector --dev; fi
if [ "${{ matrix.type }}" != "CodingStyle" ]; then composer remove --no-interaction --no-update friendsofphp/php-cs-fixer --dev; fi
if [ "${{ matrix.type }}" != "StaticAnalysis" ]; then composer remove --no-interaction --no-update phpstan/\* --dev; fi
composer update --ansi --prefer-dist --no-interaction --no-progress --optimize-autoloader
@@ -54,7 +54,7 @@ jobs:
- name: "Run tests (only for Phpunit)"
if: startsWith(matrix.type, 'Phpunit')
run: |
- vendor/bin/phpunit --exclude-group none --no-coverage -v
+ vendor/bin/phpunit --exclude-group none --no-coverage --fail-on-warning --fail-on-risky $(if vendor/bin/phpunit --version | grep -q '^PHPUnit 9\.'; then echo -v; else echo --fail-on-notice --fail-on-deprecation --display-notices --display-deprecations --display-warnings --display-errors --display-incomplete --display-skipped; fi)
- name: Check Coding Style (only for CodingStyle)
if: matrix.type == 'CodingStyle'
@@ -108,13 +108,13 @@ jobs:
- name: Install PHP dependencies
run: |
- if [ "${{ matrix.type }}" != "Phpunit" ] && [ "${{ matrix.type }}" != "Phpunit Lowest" ] && [ "${{ matrix.type }}" != "Phpunit Burn" ]; then composer remove --no-interaction --no-update phpunit/phpunit johnkary/phpunit-speedtrap --dev; fi
+ if [ "${{ matrix.type }}" != "Phpunit" ] && [ "${{ matrix.type }}" != "Phpunit Lowest" ] && [ "${{ matrix.type }}" != "Phpunit Burn" ]; then composer remove --no-interaction --no-update phpunit/phpunit atk4/ergebnis-phpunit-slow-test-detector --dev; fi
if [ "${{ matrix.type }}" != "CodingStyle" ]; then composer remove --no-interaction --no-update friendsofphp/php-cs-fixer --dev; fi
if [ "${{ matrix.type }}" != "StaticAnalysis" ]; then composer remove --no-interaction --no-update phpstan/\* --dev; fi
if [ -n "$LOG_COVERAGE" ]; then composer require --no-interaction --no-install phpunit/phpcov; fi
composer update --ansi --prefer-dist --no-interaction --no-progress --optimize-autoloader
if [ "${{ matrix.type }}" = "Phpunit Lowest" ]; then composer update --ansi --prefer-dist --prefer-lowest --prefer-stable --no-interaction --no-progress --optimize-autoloader; fi
- if [ "${{ matrix.type }}" = "Phpunit Burn" ]; then sed -i 's~ *public function runBare(): void~public function runBare(): void { gc_collect_cycles(); gc_collect_cycles(); $memDiffs = array_fill(0, '"$(if [ \"$GITHUB_EVENT_NAME\" == \"schedule\" ]; then echo 1024; else echo 64; fi)"', 0); for ($i = -1; $i < count($memDiffs); ++$i) { $this->_runBare(); gc_collect_cycles(); gc_collect_cycles(); $mem = memory_get_usage(); if ($i !== -1) { $memDiffs[$i] = $mem - $memPrev; } $memPrev = $mem; rsort($memDiffs); if (array_sum($memDiffs) >= 4096 * 1024 || $memDiffs[2] > 0) { $this->onNotSuccessfulTest(new AssertionFailedError("Memory leak detected! (" . implode(" + ", array_map(static fn ($v) => number_format($v / 1024, 3, ".", " "), array_filter($memDiffs))) . " KB, " . ($i + 2) . " iterations)")); } } } private function _runBare(): void~' vendor/phpunit/phpunit/src/Framework/TestCase.php && cat vendor/phpunit/phpunit/src/Framework/TestCase.php | grep '_runBare('; fi
+ if [ "${{ matrix.type }}" = "Phpunit Burn" ]; then sed -i 's~public function runBare(): void~public function runBare(): void { gc_collect_cycles(); gc_collect_cycles(); $memDiffs = array_fill(0, '"$(if [ \"$GITHUB_EVENT_NAME\" == \"schedule\" ]; then echo 1024; else echo 64; fi)"', 0); for ($i = -1; $i < count($memDiffs); ++$i) { $this->_runBare(); gc_collect_cycles(); gc_collect_cycles(); $mem = memory_get_usage(); if ($i !== -1) { $memDiffs[$i] = $mem - $memPrev; } $memPrev = $mem; rsort($memDiffs); if (array_sum($memDiffs) >= 4096 * 1024 || $memDiffs[2] > 0) { $this->onNotSuccessfulTest(new AssertionFailedError("Memory leak detected! (" . implode(" + ", array_map(static fn ($v) => number_format($v / 1024, 3, ".", " "), array_filter($memDiffs))) . " KB, " . ($i + 2) . " iterations)")); } } } private function _runBare(): void~' vendor/phpunit/phpunit/src/Framework/TestCase.php && cat vendor/phpunit/phpunit/src/Framework/TestCase.php | grep '_runBare('; fi
- name: Init
run: |
@@ -122,7 +122,7 @@ jobs:
- name: "Run tests"
run: |
- php -d opcache.enable_cli=1 vendor/bin/phpunit --exclude-group none $(if [ -n "$LOG_COVERAGE" ]; then echo --coverage-text; else echo --no-coverage; fi) -v
+ php -d opcache.enable_cli=1 vendor/bin/phpunit --exclude-group none $(if [ -n "$LOG_COVERAGE" ]; then echo --coverage-text; else echo --no-coverage; fi) --fail-on-warning --fail-on-risky $(if vendor/bin/phpunit --version | grep -q '^PHPUnit 9\.'; then echo -v; else echo --fail-on-notice --fail-on-deprecation --display-notices --display-deprecations --display-warnings --display-errors --display-incomplete --display-skipped; fi)
- name: Upload coverage logs 1/2 (only for coverage)
if: env.LOG_COVERAGE
diff --git a/composer.json b/composer.json
index ff292524..adb9b726 100644
--- a/composer.json
+++ b/composer.json
@@ -40,14 +40,14 @@
"symfony/yaml": "^3.4 || ^4.4 || ^5.1 || ^6.0 || ^7.0"
},
"require-dev": {
+ "atk4/ergebnis-phpunit-slow-test-detector": "^2.4",
"ergebnis/composer-normalize": "^2.13",
"friendsofphp/php-cs-fixer": "^3.0",
- "johnkary/phpunit-speedtrap": "^3.3",
"phpstan/extension-installer": "^1.1",
"phpstan/phpstan": "^1.0",
"phpstan/phpstan-deprecation-rules": "^1.0",
"phpstan/phpstan-strict-rules": "^1.3",
- "phpunit/phpunit": "^9.5.5"
+ "phpunit/phpunit": "^9.5.5 || ^10.0"
},
"minimum-stability": "dev",
"prefer-stable": true,
diff --git a/docs/container.md b/docs/container.md
index 023a57cb..90500a16 100644
--- a/docs/container.md
+++ b/docs/container.md
@@ -231,7 +231,7 @@ $args = ['name' => 'child_name']; // obsolete, backward-compatible
```
Method will return the object. Will throw exception if child with same
-name already exist.
+name already exists.
:::
:::{php:method} removeElement($shortName)
diff --git a/phpstan.neon.dist b/phpstan.neon.dist
index 6a8956de..62e7b462 100644
--- a/phpstan.neon.dist
+++ b/phpstan.neon.dist
@@ -20,3 +20,29 @@ parameters:
path: 'tests/DynamicMethodTraitTest.php'
message: '~^Call to an undefined method Atk4\\Core\\Tests\\(DynamicMethodMock|DynamicMethodWithoutHookMock)::\w+\(\)\.$~'
count: 10
+
+ # remove once PHPUnit 9.x support is removed
+ -
+ path: 'src/Phpunit/TestCase.php'
+ message: '~^Access to constant (STATUS_INCOMPLETE|STATUS_SKIPPED) on an unknown class PHPUnit\\Runner\\BaseTestRunner\.$~'
+ count: 2
+ -
+ path: 'src/Phpunit/TestCase.php'
+ message: '~^Call to an undefined method Atk4\\Core\\Phpunit\\TestCase::(getName|getStatus|getTestResultObject)\(\)\.$~'
+ count: 4
+ -
+ path: 'src/Phpunit/TestCase.php'
+ message: '~^Call to an undefined static method PHPUnit\\Util\\Test::(getLinesToBeCovered|getLinesToBeUsed)\(\)\.$~'
+ count: 2
+ -
+ path: 'tests/HookTraitTest.php'
+ message: '~^Call to an undefined method Atk4\\Core\\Tests\\HookTraitTest::getName\(\)\.$~'
+ count: 2
+ -
+ path: 'tests/Phpunit/TestCaseTest.php'
+ message: '~^Call to an undefined method Atk4\\Core\\Tests\\Phpunit\\TestCaseTest::getStatus\(\)\.$~'
+ count: 1
+ -
+ path: 'tests/Phpunit/TestCaseTest.php'
+ message: '~^Access to constant STATUS_INCOMPLETE on an unknown class PHPUnit\\Runner\\BaseTestRunner\.$~'
+ count: 1
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index 77389849..6f14faf0 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -4,14 +4,16 @@
tests
-
-
-
-
+
+
+
+
+
diff --git a/src/CollectionTrait.php b/src/CollectionTrait.php
index 95f33f77..3beaeb30 100644
--- a/src/CollectionTrait.php
+++ b/src/CollectionTrait.php
@@ -27,10 +27,10 @@ trait CollectionTrait
*
* @param string $collection property name
*/
- public function _addIntoCollection(string $name, object $item, string $collection): object
+ protected function _addIntoCollection(string $name, object $item, string $collection): object
{
if (!isset($this->{$collection}) || !is_array($this->{$collection})) {
- throw (new Exception('Collection does NOT exist'))
+ throw (new Exception('Collection does not exist'))
->addMoreInfo('collection', $collection);
}
@@ -41,11 +41,10 @@ public function _addIntoCollection(string $name, object $item, string $collectio
}
if ($this->_hasInCollection($name, $collection)) {
- throw (new Exception('Element with the same name already exist in the collection'))
+ throw (new Exception('Element with the same name already exists in the collection'))
->addMoreInfo('collection', $collection)
->addMoreInfo('name', $name);
}
- $this->{$collection}[$name] = $item;
// carry on reference to application if we have appScopeTraits set
if ((TraitUtil::hasAppScopeTrait($this) && TraitUtil::hasAppScopeTrait($item))
@@ -69,6 +68,8 @@ public function _addIntoCollection(string $name, object $item, string $collectio
}
}
+ $this->{$collection}[$name] = $item;
+
return $item;
}
@@ -77,10 +78,10 @@ public function _addIntoCollection(string $name, object $item, string $collectio
*
* @param string $collection property name
*/
- public function _removeFromCollection(string $name, string $collection): void
+ protected function _removeFromCollection(string $name, string $collection): void
{
if (!$this->_hasInCollection($name, $collection)) {
- throw (new Exception('Element is NOT in the collection'))
+ throw (new Exception('Element is not in the collection'))
->addMoreInfo('collection', $collection)
->addMoreInfo('name', $name);
}
@@ -94,7 +95,7 @@ public function _removeFromCollection(string $name, string $collection): void
*
* @param string $collectionName property name to be cloned
*/
- public function _cloneCollection(string $collectionName): void
+ protected function _cloneCollection(string $collectionName): void
{
$this->{$collectionName} = array_map(function ($item) {
$item = clone $item;
@@ -111,7 +112,7 @@ public function _cloneCollection(string $collectionName): void
*
* @param string $collection property name
*/
- public function _hasInCollection(string $name, string $collection): bool
+ protected function _hasInCollection(string $name, string $collection): bool
{
return isset($this->{$collection}[$name]);
}
@@ -119,11 +120,11 @@ public function _hasInCollection(string $name, string $collection): bool
/**
* @param string $collection property name
*/
- public function _getFromCollection(string $name, string $collection): object
+ protected function _getFromCollection(string $name, string $collection): object
{
$res = $this->{$collection}[$name] ?? null;
if ($res === null) {
- throw (new Exception('Element is NOT in the collection'))
+ throw (new Exception('Element is not in the collection'))
->addMoreInfo('collection', $collection)
->addMoreInfo('name', $name);
}
diff --git a/src/ContainerTrait.php b/src/ContainerTrait.php
index e0989432..241c91ba 100644
--- a/src/ContainerTrait.php
+++ b/src/ContainerTrait.php
@@ -24,7 +24,7 @@ trait ContainerTrait
/**
* Returns unique element name based on desired name.
*/
- public function _uniqueElementName(string $desired): string
+ protected function _uniqueElementName(string $desired): string
{
if (!isset($this->_elementNameCounts[$desired])) {
$this->_elementNameCounts[$desired] = 1;
@@ -130,7 +130,7 @@ public function removeElement($shortName)
}
if (!isset($this->elements[$shortName])) {
- throw (new Exception('Could not remove child from parent. Instead of destroy() try using removeField / removeColumn / ..'))
+ throw (new Exception('Child element not found'))
->addMoreInfo('parent', $this)
->addMoreInfo('name', $shortName);
}
diff --git a/src/DynamicMethodTrait.php b/src/DynamicMethodTrait.php
index 919be2d6..6052d8b3 100644
--- a/src/DynamicMethodTrait.php
+++ b/src/DynamicMethodTrait.php
@@ -67,7 +67,7 @@ public function addMethod(string $name, \Closure $fx)
}
if ($this->hasMethod($name)) {
- throw (new Exception('Registering method twice'))
+ throw (new Exception('Method is already defined'))
->addMoreInfo('name', $name);
}
diff --git a/src/Phpunit/TestCase.php b/src/Phpunit/TestCase.php
index 2c578b6e..b19a416f 100644
--- a/src/Phpunit/TestCase.php
+++ b/src/Phpunit/TestCase.php
@@ -7,17 +7,43 @@
use Atk4\Core\WarnDynamicPropertyTrait;
use PHPUnit\Framework\TestCase as BaseTestCase;
use PHPUnit\Framework\TestResult;
+use PHPUnit\Metadata\Api\CodeCoverage as CodeCoverageMetadata;
use PHPUnit\Runner\BaseTestRunner;
+use PHPUnit\Runner\CodeCoverage;
use PHPUnit\Util\Test as TestUtil;
-use SebastianBergmann\CodeCoverage\CodeCoverage;
+use SebastianBergmann\CodeCoverage\CodeCoverage as CodeCoverageRaw;
+
+if (\PHP_VERSION_ID >= 8_01_00) {
+ trait Phpunit9xTestCaseTrait
+ {
+ protected function onNotSuccessfulTest(\Throwable $e): never
+ {
+ $this->_onNotSuccessfulTest($e);
+ }
+ }
+} else {
+ trait Phpunit9xTestCaseTrait
+ {
+ protected function onNotSuccessfulTest(\Throwable $e): void
+ {
+ $this->_onNotSuccessfulTest($e);
+ }
+ }
+}
/**
* Generic TestCase for PHPUnit tests for ATK4 repos.
*/
abstract class TestCase extends BaseTestCase
{
+ use Phpunit9xTestCaseTrait;
use WarnDynamicPropertyTrait;
+ final public static function isPhpunit9x(): bool
+ {
+ return (new \ReflectionClass(self::class))->hasMethod('getStatus');
+ }
+
protected function setUp(): void
{
// rerun data providers to fix coverage when coverage for test files is enabled
@@ -89,15 +115,15 @@ protected function tearDown(): void
gc_collect_cycles();
// fix coverage for skipped/incomplete tests
- // based on https://github.com/sebastianbergmann/phpunit/blob/9.5.21/src/Framework/TestResult.php#L830
- // and https://github.com/sebastianbergmann/phpunit/blob/9.5.21/src/Framework/TestResult.php#L857
- if (in_array($this->getStatus(), [BaseTestRunner::STATUS_SKIPPED, BaseTestRunner::STATUS_INCOMPLETE], true)) {
- $coverage = $this->getTestResultObject()->getCodeCoverage();
+ // based on https://github.com/sebastianbergmann/phpunit/blob/9.5.21/src/Framework/TestResult.php#L830 https://github.com/sebastianbergmann/phpunit/blob/10.4.2/src/Framework/TestRunner.php#L154
+ // and https://github.com/sebastianbergmann/phpunit/blob/9.5.21/src/Framework/TestResult.php#L857 https://github.com/sebastianbergmann/phpunit/blob/10.4.2/src/Framework/TestRunner.php#L178
+ if (self::isPhpunit9x() ? in_array($this->getStatus(), [BaseTestRunner::STATUS_SKIPPED, BaseTestRunner::STATUS_INCOMPLETE], true) : $this->status()->isSkipped() || $this->status()->isIncomplete()) {
+ $coverage = self::isPhpunit9x() ? $this->getTestResultObject()->getCodeCoverage() : (CodeCoverage::instance()->isActive() ? CodeCoverage::instance() : null);
if ($coverage !== null) {
- $coverageId = \Closure::bind(static fn () => $coverage->currentId, null, CodeCoverage::class)();
+ $coverageId = self::isPhpunit9x() ? \Closure::bind(static fn () => $coverage->currentId, null, CodeCoverageRaw::class)() : (\Closure::bind(static fn () => $coverage->collecting, null, CodeCoverage::class)() ? $this : null);
if ($coverageId !== null) {
- $linesToBeCovered = TestUtil::getLinesToBeCovered(static::class, $this->getName(false));
- $linesToBeUsed = TestUtil::getLinesToBeUsed(static::class, $this->getName(false));
+ $linesToBeCovered = self::isPhpunit9x() ? TestUtil::getLinesToBeCovered(static::class, $this->getName(false)) : (new CodeCoverageMetadata())->linesToBeCovered(static::class, $this->name());
+ $linesToBeUsed = self::isPhpunit9x() ? TestUtil::getLinesToBeUsed(static::class, $this->getName(false)) : (new CodeCoverageMetadata())->linesToBeUsed(static::class, $this->name());
$coverage->stop(true, $linesToBeCovered, $linesToBeUsed);
$coverage->start($coverageId);
}
@@ -131,7 +157,10 @@ private function releaseObjectsFromExceptionTrace(\Throwable $e): void
}
}
- protected function onNotSuccessfulTest(\Throwable $e): void
+ /**
+ * @return never
+ */
+ protected function _onNotSuccessfulTest(\Throwable $e): void
{
// release objects from uncaught exception as it is never released
$this->releaseObjectsFromExceptionTrace($e);
diff --git a/src/TraitUtil.php b/src/TraitUtil.php
index d7300507..69a6a411 100644
--- a/src/TraitUtil.php
+++ b/src/TraitUtil.php
@@ -29,7 +29,7 @@ public static function hasTrait($class, string $traitName): bool
// prevent mass use for other than internal use then we can decide
// if we want to keep support this or replace with pure interfaces
if (!str_starts_with($traitName, 'Atk4\Core\\')) {
- throw new Exception(self::class . '::hasTrait is not intended for use with other than Atk4\Core\* traits');
+ throw new Exception(self::class . '::hasTrait() method is not intended for use with other than Atk4\Core\* traits');
}
$parentClass = get_parent_class($class);
diff --git a/src/TranslatableTrait.php b/src/TranslatableTrait.php
index 54a8ef7d..e354a186 100644
--- a/src/TranslatableTrait.php
+++ b/src/TranslatableTrait.php
@@ -12,7 +12,7 @@
trait TranslatableTrait
{
/**
- * Translates the given message.
+ * Translate the given message.
*
* @param string $message The message to be translated
* @param array $parameters Array of parameters used to translate message
diff --git a/tests/CollectionMock.php b/tests/CollectionMock.php
index f46bc280..0ee13aaf 100644
--- a/tests/CollectionMock.php
+++ b/tests/CollectionMock.php
@@ -15,7 +15,7 @@ class CollectionMock
protected $fields = [];
/**
- * @param array|object|null $seed
+ * @param array|FieldMock|null $seed
*/
public function addField(string $name, $seed = null): FieldMock
{
diff --git a/tests/CollectionTraitTest.php b/tests/CollectionTraitTest.php
index 44573508..ccf33b2f 100644
--- a/tests/CollectionTraitTest.php
+++ b/tests/CollectionTraitTest.php
@@ -5,7 +5,6 @@
namespace Atk4\Core\Tests;
use Atk4\Core\AppScopeTrait;
-use Atk4\Core\DiContainerTrait;
use Atk4\Core\Exception;
use Atk4\Core\InitializerTrait;
use Atk4\Core\NameTrait;
@@ -32,9 +31,6 @@ public function testBasic(): void
self::assertFalse($m->hasField('name'));
}
- /**
- * Test Trackable and AppScope.
- */
public function testBasicWithApp(): void
{
$m = new CollectionMockWithApp();
@@ -60,14 +56,31 @@ public function testBasicWithApp(): void
self::assertSame(40, strlen($longField->name));
}
+ public function testCloneCollection(): void
+ {
+ $m = new CollectionMock();
+ $m->addField('a', [FieldMock::class]);
+ $m->addField('b', [FieldMockCustom::class]);
+
+ $mCloned = clone $m;
+ \Closure::bind(static fn () => $m->_cloneCollection('fields'), null, CollectionMock::class)();
+
+ self::assertNotSame($m->getField('a'), $mCloned->getField('a'));
+ self::assertNotSame($m->getField('b'), $mCloned->getField('b'));
+ self::assertSame('b', $m->getField('b')->shortName); // @phpstan-ignore-line
+ self::assertSame('b', $mCloned->getField('b')->shortName); // @phpstan-ignore-line
+ }
+
/**
* Bad collection name.
*/
public function testException1(): void
{
- $this->expectException(Exception::class);
$m = new CollectionMock();
- $m->_addIntoCollection('foo', (object) [], ''); // empty collection name
+
+ $this->expectException(Exception::class);
+ $this->expectExceptionMessage('Collection does not exist');
+ \Closure::bind(static fn () => $m->_addIntoCollection('foo', (object) [], ''), null, CollectionMock::class)(); // empty collection name
}
/**
@@ -75,9 +88,11 @@ public function testException1(): void
*/
public function testException2(): void
{
- $this->expectException(Exception::class);
$m = new CollectionMock();
- $m->_addIntoCollection('', (object) [], 'fields'); // empty object name
+
+ $this->expectException(Exception::class);
+ $this->expectExceptionMessage('Empty name is not supported');
+ \Closure::bind(static fn () => $m->_addIntoCollection('', (object) [], 'fields'), null, CollectionMock::class)(); // empty object name
}
/**
@@ -85,45 +100,46 @@ public function testException2(): void
*/
public function testException3(): void
{
- $this->expectException(Exception::class);
$m = new CollectionMock();
- $m->_addIntoCollection('foo', (object) [], 'fields');
- $m->_addIntoCollection('foo', (object) [], 'fields'); // already exists
+ \Closure::bind(static fn () => $m->_addIntoCollection('foo', (object) [], 'fields'), null, CollectionMock::class)();
+
+ $this->expectException(Exception::class);
+ $this->expectExceptionMessage('Element with the same name already exists in the collection');
+ \Closure::bind(static fn () => $m->_addIntoCollection('foo', (object) [], 'fields'), null, CollectionMock::class)(); // already exists
}
/**
- * Cannot remove non existent object.
+ * Cannot get non existent object.
*/
public function testException4(): void
{
- $this->expectException(Exception::class);
$m = new CollectionMock();
- $m->_removeFromCollection('dont_exist', 'fields'); // do not exist
+
+ $this->expectException(Exception::class);
+ $this->expectExceptionMessage('Element is not in the collection');
+ \Closure::bind(static fn () => $m->_getFromCollection('dont_exist', 'fields'), null, CollectionMock::class)(); // does not exist
}
/**
- * Cannot get non existent object.
+ * Cannot remove non existent object.
*/
public function testException5(): void
{
- $this->expectException(Exception::class);
$m = new CollectionMock();
- $m->_getFromCollection('dont_exist', 'fields'); // do not exist
+
+ $this->expectException(Exception::class);
+ $this->expectExceptionMessage('Element is not in the collection');
+ \Closure::bind(static fn () => $m->_removeFromCollection('dont_exist', 'fields'), null, CollectionMock::class)(); // does not exist
}
- /**
- * Cannot get non existent object.
- */
public function testException6(): void
{
- $this->expectException(Exception::class);
$m = new CollectionMock();
- $m->addField('test', new class() {
- use DiContainerTrait;
- use InitializerTrait;
- /** @var string */
- public $name;
+ $this->expectException(Exception::class);
+ $this->expectExceptionMessage('Object was not initialized');
+ $m->addField('test', new class() extends FieldMock {
+ use InitializerTrait;
protected function init(): void {}
});
diff --git a/tests/ConfigTraitTest.php b/tests/ConfigTraitTest.php
index f39f641b..13a1d1b3 100644
--- a/tests/ConfigTraitTest.php
+++ b/tests/ConfigTraitTest.php
@@ -77,22 +77,28 @@ public function testFileRead(): void
public function testFileReadException(): void
{
- $this->expectException(Exception::class);
$m = new ConfigMock();
+
+ $this->expectException(Exception::class);
+ $this->expectExceptionMessage('Cannot read config file');
$m->readConfig('unknown_file.php');
}
public function testFileBadFormatException(): void
{
- $this->expectException(Exception::class);
$m = new ConfigMock();
+
+ $this->expectException(Exception::class);
+ $this->expectExceptionMessage('File was read but has a bad format');
$m->readConfig($this->dir . '/config_bad_format.php');
}
public function testWrongFileFormatException(): void
{
- $this->expectException(Exception::class);
$m = new ConfigMock();
+
+ $this->expectException(Exception::class);
+ $this->expectExceptionMessage('Unknown Format. Allowed formats: php, json, yml');
$m->readConfig($this->dir . '/config.yml', 'wrong-format');
}
diff --git a/tests/ContainerTraitTest.php b/tests/ContainerTraitTest.php
index 4b0b92f7..4715dc7e 100644
--- a/tests/ContainerTraitTest.php
+++ b/tests/ContainerTraitTest.php
@@ -142,6 +142,7 @@ public function __construct(string $name, bool $isLongName)
self::assertSame('foo', $app->add($createTrackableMockFx('foo', true))->name);
$this->expectException(Exception::class);
+ $this->expectExceptionMessage('Element has too long desired name');
self::assertSame(40, strlen($app->add($createTrackableMockFx(str_repeat('x', 100), true))->name));
}
@@ -204,9 +205,11 @@ public function testArgs(): void
public function testExceptionExists(): void
{
- $this->expectException(Exception::class);
$m = new ContainerMock();
$m->add(new TrackableMock(), 'foo');
+
+ $this->expectException(Exception::class);
+ $this->expectExceptionMessage('Element with requested name already exists');
$m->add(new TrackableMock(), 'foo');
}
@@ -221,14 +224,14 @@ public function testDesiredName(): void
public function testExceptionShortName(): void
{
- $this->expectException(Exception::class);
$m1 = new ContainerMock();
$m2 = new ContainerMock();
$m1foo = $m1->add(new TrackableMock(), 'foo');
$m2foo = $m2->add(new TrackableMock(), 'foo');
- // will carry on short name and run into collision.
- $m2->add($m1foo);
+ $this->expectException(Exception::class);
+ $this->expectExceptionMessage('Element with requested name already exists');
+ $m2->add($m1foo); // will carry on short name and run into collision
}
public function testExceptionArg2(): void
@@ -236,12 +239,13 @@ public function testExceptionArg2(): void
$m = new ContainerMock();
if (\PHP_MAJOR_VERSION === 7) {
- $this->expectWarning(); // @phpstan-ignore-line
- $this->expectWarningMessage('array_diff_key(): Expected parameter 1 to be an array, int given'); // @phpstan-ignore-line
- } else {
- $this->expectException(\TypeError::class);
- $this->expectExceptionMessage('array_diff_key(): Argument #1 ($array) must be of type array, int given');
+ self::assertNotNull('Expecting E_WARNING is deprecated in PHPUnit 9'); // @phpstan-ignore-line
+
+ return;
}
+
+ $this->expectException(\TypeError::class);
+ $this->expectExceptionMessage('array_diff_key(): Argument #1 ($array) must be of type array, int given');
$m->add(new TrackableMock(), 123); // @phpstan-ignore-line
}
@@ -259,13 +263,16 @@ public function testException4(): void
$m = new ContainerMock();
$this->expectException(Exception::class);
+ $this->expectExceptionMessage('Child element not found');
$m->getElement('dont_exist');
}
public function testException5(): void
{
- $this->expectException(Exception::class);
$m = new ContainerMock();
+
+ $this->expectException(Exception::class);
+ $this->expectExceptionMessage('Child element not found');
$m->removeElement('dont_exist');
}
}
diff --git a/tests/DiContainerTraitTest.php b/tests/DiContainerTraitTest.php
index c67ba5cf..368e0e85 100644
--- a/tests/DiContainerTraitTest.php
+++ b/tests/DiContainerTraitTest.php
@@ -16,20 +16,25 @@ public function testFromSeed(): void
self::assertSame(StdSat2::class, get_class(StdSat::fromSeed([StdSat2::class])));
$this->expectException(Exception::class);
+ $this->expectExceptionMessage('Seed class is not a subtype of static class');
StdSat2::fromSeed([StdSat::class]);
}
public function testNoPropExStandard(): void
{
- $this->expectException(Exception::class);
$m = new FactoryDiMock2();
+
+ $this->expectException(Exception::class);
+ $this->expectExceptionMessage('Property for specified object is not defined');
$m->setDefaults(['not_exist' => 'qwerty']);
}
public function testNoPropExNumeric(): void
{
- $this->expectException(Exception::class);
$m = new FactoryDiMock2();
+
+ $this->expectException(Exception::class);
+ $this->expectExceptionMessage('Property for specified object is not defined');
$m->setDefaults([5 => 'qwerty']); // @phpstan-ignore-line
}
@@ -81,10 +86,14 @@ public function testPassively(): void
public function testInstanceOfBeforeConstructor(): void
{
+ $catchCalled = false;
try {
new FactoryDiMockConstructorMustNeverBeCalled();
} catch (\Error $e) {
+ $catchCalled = true;
}
+ self::assertTrue($catchCalled);
+
$this->expectException(Exception::class);
$this->expectExceptionMessage('Seed class is not a subtype of static class');
FactoryDiMockConstructorMustNeverBeCalled2::fromSeed([FactoryDiMockConstructorMustNeverBeCalled::class]);
diff --git a/tests/DynamicMethodTraitTest.php b/tests/DynamicMethodTraitTest.php
index 0714e345..ca6c25f6 100644
--- a/tests/DynamicMethodTraitTest.php
+++ b/tests/DynamicMethodTraitTest.php
@@ -71,6 +71,7 @@ public function testExceptionUndefinedWithoutHookTrait(): void
$m = new DynamicMethodWithoutHookMock();
$this->expectException(\Error::class);
+ $this->expectExceptionMessage('Call to undefined method ' . DynamicMethodWithoutHookMock::class . '::unknownMethod()');
$m->unknownMethod();
}
@@ -79,6 +80,7 @@ public function testExceptionAddWithoutHookTrait(): void
$m = new DynamicMethodWithoutHookMock();
$this->expectException(Exception::class);
+ $this->expectExceptionMessage('Object must use HookTrait for dynamic method support');
$m->addMethod('sum', $this->createSumFx());
}
@@ -101,6 +103,7 @@ public function testDoubleMethodException(): void
$m->addMethod('sum', $this->createSumFx());
$this->expectException(Exception::class);
+ $this->expectExceptionMessage('Method is already defined');
$m->addMethod('sum', $this->createSumFx());
}
diff --git a/tests/HookTraitTest.php b/tests/HookTraitTest.php
index 27ca9281..af8aedbf 100644
--- a/tests/HookTraitTest.php
+++ b/tests/HookTraitTest.php
@@ -377,7 +377,7 @@ public function testOnHookDynamicBoundGetterException(): void
$this->expectException(\TypeError::class);
$this->expectExceptionMessage('New $this getter must be static');
$m->onHookDynamic('inc', function (HookMock $m) {
- $this->getName(); // prevent PHP CS Fixer to make this anonymous function static
+ self::isPhpunit9x() ? $this->getName(false) : $this->name(); // prevent PHP CS Fixer to make this anonymous function static
return $m;
}, $m->makeIncrementResultFx());
@@ -411,7 +411,7 @@ public function testPassByReference(): void
$value = 0;
$m = new HookMock();
$m->onHookShort('inc', function ($ignore1st, int &$value) {
- $this->getName(); // prevent PHP CS Fixer to make this anonymous function static
+ self::isPhpunit9x() ? $this->getName(false) : $this->name(); // prevent PHP CS Fixer to make this anonymous function static
++$value;
});
diff --git a/tests/Phpunit/TestCaseTest.php b/tests/Phpunit/TestCaseTest.php
index 8af95206..c8c956af 100644
--- a/tests/Phpunit/TestCaseTest.php
+++ b/tests/Phpunit/TestCaseTest.php
@@ -7,6 +7,7 @@
use Atk4\Core\Exception;
use Atk4\Core\Phpunit\TestCase;
use PHPUnit\Framework\TestCase as PhpunitTestCase;
+use PHPUnit\Framework\TestStatus\TestStatus;
use PHPUnit\Runner\BaseTestRunner;
class TestCaseTest extends TestCase
@@ -105,7 +106,7 @@ public function testObjectsAreReleasedFromUncaughtException(): void
/**
* @return iterable>
*/
- public function provideProviderAbCases(): iterable
+ public static function provideProviderAbCases(): iterable
{
yield ['a'];
yield ['b'];
@@ -126,7 +127,7 @@ public function testProviderCoverage(string $v): void
/**
* @return iterable>
*/
- public function provideProviderCoverageCases(): iterable
+ public static function provideProviderCoverageCases(): iterable
{
yield ['x'];
++self::$providerCoverageCallCounter;
@@ -138,8 +139,8 @@ public function provideProviderCoverageCases(): iterable
*/
public function testCoverageImplForTestMarkedAsIncomplete(): void
{
- $testStatusOrig = \Closure::bind(fn () => $this->status, $this, PhpunitTestCase::class)();
- \Closure::bind(fn () => $this->status = BaseTestRunner::STATUS_INCOMPLETE, $this, PhpunitTestCase::class)();
+ $testStatusOrig = self::isPhpunit9x() ? $this->getStatus() : $this->status();
+ \Closure::bind(fn () => $this->status = TestCase::isPhpunit9x() ? BaseTestRunner::STATUS_INCOMPLETE : TestStatus::incomplete(), $this, PhpunitTestCase::class)();
try {
$this->tearDown();
} finally {
diff --git a/tests/TraitUtilTest.php b/tests/TraitUtilTest.php
index 72769997..3854c9d0 100644
--- a/tests/TraitUtilTest.php
+++ b/tests/TraitUtilTest.php
@@ -4,10 +4,12 @@
namespace Atk4\Core\tests;
+use Atk4\Core\Exception;
use Atk4\Core\HookTrait;
use Atk4\Core\NameTrait;
use Atk4\Core\Phpunit\TestCase;
use Atk4\Core\TraitUtil;
+use PHPUnit\Framework\MockObject\Method as MockObjectMethodTrait;
class TraitUtilTest extends TestCase
{
@@ -29,6 +31,13 @@ public function testHasTrait(): void
self::assertTrue(TraitUtil::hasTrait(TraitUtilTestB::class, HookTrait::class));
self::assertTrue(TraitUtil::hasTrait(TraitUtilTestC::class, HookTrait::class));
}
+
+ public function testHasTraitNoAtk4CoreException(): void
+ {
+ $this->expectException(Exception::class);
+ $this->expectExceptionMessage(TraitUtil::class . '::hasTrait() method is not intended for use with other than Atk4\Core\* traits');
+ TraitUtil::hasTrait(TraitUtilTestA::class, MockObjectMethodTrait::class);
+ }
}
class TraitUtilTestA {}
diff --git a/tests/Translator/AdapterAppTest.php b/tests/Translator/AdapterAppTest.php
index a81a1363..7e20f03c 100644
--- a/tests/Translator/AdapterAppTest.php
+++ b/tests/Translator/AdapterAppTest.php
@@ -7,7 +7,7 @@
use Atk4\Core\AppScopeTrait;
use Atk4\Core\TranslatableTrait;
-class AdapterAppTest extends AdapterBaseTest
+class AdapterAppTest extends AdapterTestCase
{
public function getTranslatableMock(): object
{
diff --git a/tests/Translator/AdapterGenericTest.php b/tests/Translator/AdapterGenericTest.php
index 5c3804c9..2cc2ade3 100644
--- a/tests/Translator/AdapterGenericTest.php
+++ b/tests/Translator/AdapterGenericTest.php
@@ -8,7 +8,7 @@
use Atk4\Core\Translator\Adapter\Generic;
use Atk4\Core\Translator\Translator;
-class AdapterGenericTest extends AdapterBaseTest
+class AdapterGenericTest extends AdapterTestCase
{
public function getTranslatableMock(): object
{
diff --git a/tests/Translator/AdapterBaseTest.php b/tests/Translator/AdapterTestCase.php
similarity index 98%
rename from tests/Translator/AdapterBaseTest.php
rename to tests/Translator/AdapterTestCase.php
index 83b62e97..d9922a52 100644
--- a/tests/Translator/AdapterBaseTest.php
+++ b/tests/Translator/AdapterTestCase.php
@@ -8,7 +8,7 @@
use Atk4\Core\Translator\Adapter\Generic;
use Atk4\Core\Translator\Translator;
-abstract class AdapterBaseTest extends TestCase
+abstract class AdapterTestCase extends TestCase
{
abstract public function getTranslatableMock(): object;
diff --git a/tests/WarnDynamicPropertyTraitTest.php b/tests/WarnDynamicPropertyTraitTest.php
index 7fd5157a..4faa622f 100644
--- a/tests/WarnDynamicPropertyTraitTest.php
+++ b/tests/WarnDynamicPropertyTraitTest.php
@@ -131,4 +131,4 @@ public function testGetSetWithWarningSuppressed(): void
}
}
-class WarnError extends \Exception {}
+class WarnError extends \ErrorException {}