From 90f62205d46eedb5866148cb392f66964ab2fb76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felipe=20Say=C3=A3o=20Lobato=20Abreu?= Date: Wed, 11 Jun 2025 11:15:31 -0300 Subject: [PATCH 1/4] Add ArrayAccess interface to ConfigInterface --- .github/workflows/tests.yml | 78 +++++++++++++++++++ composer.json | 4 +- src/ArrayAccessConfigTrait.php | 87 +++++++++++++++++++++ src/ArrayConfig.php | 16 ++++ src/CachedConfig.php | 19 +++++ src/ConfigInterface.php | 12 ++- src/LazyLoadConfigTrait.php | 12 +++ tests/ArrayAccessConfigTraitTest.php | 110 +++++++++++++++++++++++++++ tests/ArrayConfigTest.php | 44 +++++++++++ tests/CachedConfigTest.php | 36 +++++++++ tests/Helper/ConfigHelperTest.php | 20 ++++- tests/LazyLoadConfigTraitTest.php | 29 +++++++ 12 files changed, 461 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/tests.yml create mode 100644 src/ArrayAccessConfigTrait.php create mode 100644 tests/ArrayAccessConfigTraitTest.php diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..0b62251 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,78 @@ +name: Run PHPUnit Tests + +on: + workflow_dispatch: + pull_request: + push: + branches: [ "main" ] + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + tests: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Cache Composer dependencies + uses: actions/cache@v3 + with: + path: /tmp/composer-cache + key: ${{ runner.os }}-${{ hashFiles('**/composer.lock') }} + + - name: Install dependencies + uses: php-actions/composer@v6 + env: + COMPOSER_AUTH: '{"github-oauth": {"github.com": "${{ github.token }}"} }' + with: + php_version: '8.4' + + - name: Run PHPUnit tests + uses: php-actions/phpunit@v3 + env: + XDEBUG_MODE: coverage + with: + php_version: '8.4' + php_extensions: pcov + + - name: Ensure minimum code coverage + env: + MINIMUM_COVERAGE: 80 + run: | + COVERAGE=$(php -r ' + $xml = new SimpleXMLElement(file_get_contents("public/coverage/clover.xml")); + $m = $xml->project->metrics; + $pct = (int) round(((int) $m["coveredstatements"]) * 100 / (int) $m["statements"]); + echo $pct; + ') + echo "Coverage: ${COVERAGE}%" + if [ "${COVERAGE}" -lt ${{ env.MINIMUM_COVERAGE }} ]; then + echo "Code coverage below ${{ env.MINIMUM_COVERAGE }}% threshold." + exit 1 + fi + + - name: Upload artifact + if: github.ref == 'refs/heads/main' + uses: actions/upload-pages-artifact@v3 + with: + path: public/coverage + + deploy: + if: github.ref == 'refs/heads/main' + needs: tests + environment: + name: code-coverage + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/composer.json b/composer.json index a6a9cc3..e3a8fab 100644 --- a/composer.json +++ b/composer.json @@ -49,8 +49,8 @@ } }, "scripts": { - "cs-check": "php-cs-fixer fix --dry-run --diff", - "cs-fix": "php-cs-fixer fix", + "cs-check": "PHP_CS_FIXER_IGNORE_ENV=1 php-cs-fixer fix --dry-run --diff", + "cs-fix": "PHP_CS_FIXER_IGNORE_ENV=1 php-cs-fixer fix", "mutation-testing": "infection --threads=4", "pre-commit": [ "@cs-check", diff --git a/src/ArrayAccessConfigTrait.php b/src/ArrayAccessConfigTrait.php new file mode 100644 index 0000000..accc99f --- /dev/null +++ b/src/ArrayAccessConfigTrait.php @@ -0,0 +1,87 @@ + + * @license https://opensource.org/licenses/MIT MIT License + */ + +namespace FastForward\Config; + +use Dflydev\DotAccessData\Data; + +/** + * Trait ArrayAccessConfigTrait. + * + * This trait provides array-like access to configuration data. + * It MUST be used in classes that implement \ArrayAccess and provide + * the corresponding methods: `get`, `set`, `has`, and `remove`. + * + * @internal + * + * @see \ArrayAccess + */ +trait ArrayAccessConfigTrait +{ + /** + * Determines whether the given offset exists in the configuration data. + * + * This method SHALL return true if the offset is present, false otherwise. + * + * @param mixed $offset the offset to check for existence + * + * @return bool true if the offset exists, false otherwise + */ + public function offsetExists(mixed $offset): bool + { + return $this->has($offset); + } + + /** + * Retrieves the value associated with the given offset. + * + * This method MUST return the value mapped to the specified offset. + * If the offset does not exist, behavior SHALL depend on the implementation + * of the `get` method. + * + * @param mixed $offset the offset to retrieve + * + * @return mixed the value at the given offset + */ + public function offsetGet(mixed $offset): mixed + { + return $this->get($offset); + } + + /** + * Sets the value for the specified offset. + * + * This method SHALL assign the given value to the specified offset. + * + * @param mixed $offset the offset at which to set the value + * @param mixed $value the value to set + */ + public function offsetSet(mixed $offset, mixed $value): void + { + $this->set($offset, $value); + } + + /** + * Unsets the specified offset. + * + * This method SHALL remove the specified offset and its associated value. + * + * @param mixed $offset the offset to remove + */ + public function offsetUnset(mixed $offset): void + { + $this->remove($offset); + } +} diff --git a/src/ArrayConfig.php b/src/ArrayConfig.php index 5a7f40d..7838345 100644 --- a/src/ArrayConfig.php +++ b/src/ArrayConfig.php @@ -30,6 +30,8 @@ */ final class ArrayConfig implements ConfigInterface { + use ArrayAccessConfigTrait; + /** * @var Data internal configuration storage instance */ @@ -111,6 +113,20 @@ public function set(array|ConfigInterface|string $key, mixed $value = null): voi $this->data->import(ConfigHelper::normalize($key)); } + /** + * Removes a configuration key and its associated value. + * + * If the key does not exist, this method SHALL do nothing. + * + * @param string $key the configuration key to remove + */ + public function remove(string $key): void + { + if ($this->has($key)) { + $this->data->remove($key); + } + } + /** * Retrieves a traversable set of flattened configuration data. * diff --git a/src/CachedConfig.php b/src/CachedConfig.php index 4443f99..5432326 100644 --- a/src/CachedConfig.php +++ b/src/CachedConfig.php @@ -87,4 +87,23 @@ public function set(array|ConfigInterface|string $key, mixed $value = null): voi $this->cache->set($this->cacheKey, $config->toArray()); } } + + /** + * Retrieves a configuration value by key. + * + * This method MUST return the cached value if it exists, or the default value if not found. + * + * @param string $key the configuration key to retrieve + * + * @return mixed the configuration value or the default value + */ + public function remove(mixed $key): void + { + $config = $this->getConfig(); + $config->remove($key); + + if ($this->persistent) { + $this->cache->set($this->cacheKey, $config->toArray()); + } + } } diff --git a/src/ConfigInterface.php b/src/ConfigInterface.php index f98d5d5..eaf9233 100644 --- a/src/ConfigInterface.php +++ b/src/ConfigInterface.php @@ -27,7 +27,7 @@ * Keys MAY use dot notation to access nested structures, e.g., `my.next.key` * corresponds to ['my' => ['next' => ['key' => $value]]]. */ -interface ConfigInterface extends \IteratorAggregate +interface ConfigInterface extends \IteratorAggregate, \ArrayAccess { /** * Determines if the specified key exists in the configuration. @@ -68,6 +68,16 @@ public function get(string $key, mixed $default = null): mixed; */ public function set(array|self|string $key, mixed $value = null): void; + /** + * Removes a configuration key and its associated value. + * + * Dot notation MAY be used to specify nested keys. + * If the key does not exist, this method MUST do nothing. + * + * @param string $key the configuration key to remove + */ + public function remove(string $key): void; + /** * Exports the configuration as a nested associative array. * diff --git a/src/LazyLoadConfigTrait.php b/src/LazyLoadConfigTrait.php index 393606f..0e2fb9a 100644 --- a/src/LazyLoadConfigTrait.php +++ b/src/LazyLoadConfigTrait.php @@ -24,6 +24,8 @@ */ trait LazyLoadConfigTrait { + use ArrayAccessConfigTrait; + /** * @var null|ConfigInterface holds the loaded configuration instance */ @@ -72,6 +74,16 @@ public function set(array|ConfigInterface|string $key, mixed $value = null): voi $this->getConfig()->set($key, $value); } + /** + * Removes a configuration key. + * + * @param string $key the configuration key to remove + */ + public function remove(string $key): void + { + $this->getConfig()->remove($key); + } + /** * Exports the entire configuration to an array. * diff --git a/tests/ArrayAccessConfigTraitTest.php b/tests/ArrayAccessConfigTraitTest.php new file mode 100644 index 0000000..dbb3a76 --- /dev/null +++ b/tests/ArrayAccessConfigTraitTest.php @@ -0,0 +1,110 @@ + + * @license https://opensource.org/licenses/MIT MIT License + */ + +use FastForward\Config\ArrayAccessConfigTrait; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\TestCase; +use Prophecy\PhpUnit\ProphecyTrait; + +/** + * @internal + */ +#[CoversClass(ArrayAccessConfigTrait::class)] +final class ArrayAccessConfigTraitTest extends TestCase +{ + use ProphecyTrait; + + public function testOffsetExistsWillCallHasMethod(): void + { + $object = $this->createTraitInstance(has: true); + self::assertTrue($object->offsetExists('key')); + + $object = $this->createTraitInstance(has: false); + self::assertFalse($object->offsetExists('key')); + } + + public function testOffsetGetWillCallGetMethod(): void + { + $object = $this->createTraitInstance(get: 'foo'); + self::assertSame('foo', $object->offsetGet('bar')); + } + + public function testOffsetSetWillCallSetMethod(): void + { + $object = $this->createTraitInstance(); + $object->offsetSet('alpha', 'beta'); + + self::assertSame(['alpha' => 'beta'], $object->getSetCalls()); + } + + public function testOffsetUnsetWillCallRemoveMethod(): void + { + $object = $this->createTraitInstance(); + $object->offsetUnset('delta'); + + self::assertSame(['delta'], $object->getRemoveCalls()); + } + + private function createTraitInstance(bool $has = false, mixed $get = null): ArrayAccess + { + return new class($has, $get) implements ArrayAccess { + use ArrayAccessConfigTrait; + + private array $setCalls = []; + + private array $removeCalls = []; + + private bool $hasReturn; + + private mixed $getReturn; + + public function __construct(bool $has, mixed $get) + { + $this->hasReturn = $has; + $this->getReturn = $get; + } + + public function has(mixed $offset): bool + { + return $this->hasReturn; + } + + public function get(mixed $offset): mixed + { + return $this->getReturn; + } + + public function set(mixed $offset, mixed $value): void + { + $this->setCalls[$offset] = $value; + } + + public function remove(mixed $offset): void + { + $this->removeCalls[] = $offset; + } + + public function getSetCalls(): array + { + return $this->setCalls; + } + + public function getRemoveCalls(): array + { + return $this->removeCalls; + } + }; + } +} diff --git a/tests/ArrayConfigTest.php b/tests/ArrayConfigTest.php index c709ff7..f3547e4 100644 --- a/tests/ArrayConfigTest.php +++ b/tests/ArrayConfigTest.php @@ -144,4 +144,48 @@ public function testDotNotationMergesAssociativeNestedKeys(): void self::assertSame($expected, $config->toArray()); } + + #[Test] + public function testRemoveKeyRemovesTopLevelKeys(): void + { + $key = uniqid('key_'); + $val = uniqid('value_'); + + $config = new ArrayConfig([$key => $val]); + $config->remove($key); + + self::assertFalse($config->has($key)); + self::assertSame([], $config->toArray()); + } + + #[Test] + public function testRemoveKeyRemovesNestedKeys(): void + { + $key1 = uniqid('key1.'); + $key2 = $key1 . 'nested'; + $val = uniqid('value_'); + + $config = new ArrayConfig([$key2 => $val]); + $config->remove($key2); + + self::assertFalse($config->has($key2)); + self::assertSame(['key1' => []], $config->toArray()); + } + + #[Test] + public function testRemoveKeyRemovesDeepNestedKeys(): void + { + $key1 = uniqid('key1.'); + $key2 = $key1 . '.nested.'; + $key3 = $key2 . 'deep'; + $val = uniqid('value_'); + + $config = new ArrayConfig([$key3 => $val]); + $config->remove($key3); + + $config2 = new ArrayConfig([$key1 => ['nested' => []]]); + + self::assertFalse($config->has($key3)); + self::assertSame($config2->toArray(), $config->toArray()); + } } diff --git a/tests/CachedConfigTest.php b/tests/CachedConfigTest.php index 6ba78bd..9177b07 100644 --- a/tests/CachedConfigTest.php +++ b/tests/CachedConfigTest.php @@ -122,4 +122,40 @@ public function testSetWillNotUpdateCacheWhenPersistentIsFalse(): void $cachedConfig->set($key, $value); } + + #[Test] + public function testRemoveWillUpdateCacheWhenPersistentIsTrue(): void + { + $key = uniqid(); + + $this->cache->has($this->defaultConfig->reveal()::class)->willReturn(true); + $this->cache->get($this->defaultConfig->reveal()::class)->willReturn([$key => uniqid()]); + + $this->cache->set($this->defaultConfig->reveal()::class, [])->shouldBeCalled(); + + $this->cachedConfig->remove($key); + + self::assertFalse($this->cachedConfig->has($key)); + } + + #[Test] + public function testRemoveWillNotUpdateCacheWhenPersistentIsFalse(): void + { + $cachedConfig = new CachedConfig( + cache: $this->cache->reveal(), + defaultConfig: $this->defaultConfig->reveal(), + persistent: false + ); + + $key = uniqid(); + + $this->cache->has($this->defaultConfig->reveal()::class)->willReturn(true); + $this->cache->get($this->defaultConfig->reveal()::class)->willReturn([$key => uniqid()]); + + $this->cache->set($this->defaultConfig->reveal()::class, [])->shouldNotBeCalled(); + + $cachedConfig->remove($key); + + self::assertFalse($cachedConfig->has($key)); + } } diff --git a/tests/Helper/ConfigHelperTest.php b/tests/Helper/ConfigHelperTest.php index 395b473..9d8eab6 100644 --- a/tests/Helper/ConfigHelperTest.php +++ b/tests/Helper/ConfigHelperTest.php @@ -2,6 +2,17 @@ declare(strict_types=1); +/** + * This file is part of php-fast-forward/config. + * + * This source file is subject to the license bundled + * with this source code in the file LICENSE. + * + * @link https://github.com/php-fast-forward/config + * @copyright Copyright (c) 2025 Felipe Sayão Lobato Abreu + * @license https://opensource.org/licenses/MIT MIT License + */ + namespace FastForward\Config\Tests\Helper; use FastForward\Config\Helper\ConfigHelper; @@ -9,6 +20,9 @@ use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; +/** + * @internal + */ #[CoversClass(ConfigHelper::class)] final class ConfigHelperTest extends TestCase { @@ -65,7 +79,7 @@ public function testNormalizeWillConvertDotNotationToNestedArray(): void public function testNormalizeWillHandleMixedNestedArrays(): void { $input = [ - 'database.host' => 'localhost', + 'database.host' => 'localhost', 'database.config' => ['port' => 3306], ]; @@ -95,7 +109,7 @@ public function testNormalizeWillReturnIndexedArrayUnchanged(): void public function testNormalizeWillMergeArraysWhenKeysOverlap(): void { $input = [ - 'settings' => ['display' => ['resolution' => '1080p']], + 'settings' => ['display' => ['resolution' => '1080p']], 'settings.display' => ['theme' => 'dark'], ]; @@ -110,7 +124,7 @@ public function testNormalizeWillMergeArraysWhenKeysOverlap(): void $result = ConfigHelper::normalize($input); - self::assertEquals($expected, $result); + self::assertSame($expected, $result); } #[Test] diff --git a/tests/LazyLoadConfigTraitTest.php b/tests/LazyLoadConfigTraitTest.php index c31548d..333f44a 100644 --- a/tests/LazyLoadConfigTraitTest.php +++ b/tests/LazyLoadConfigTraitTest.php @@ -15,6 +15,7 @@ namespace FastForward\Config\Tests; +use FastForward\Config\ArrayAccessConfigTrait; use FastForward\Config\ConfigInterface; use FastForward\Config\LazyLoadConfigTrait; use PHPUnit\Framework\Attributes\CoversClass; @@ -43,6 +44,25 @@ public function testGetDelegatesToConfig(): void self::assertSame($default, $fake->get(uniqid('missing_', true), $default)); } + #[Test] + public function testRemoveRemovesFromConfig(): void + { + $key1 = uniqid('key1_', true); + $key2 = uniqid('key2_', true); + $val1 = random_int(1, 100); + $val2 = random_int(101, 200); + + $fake = $this->createTestInstance([$key1 => $val1, $key2 => $val2]); + + self::assertTrue($fake->has($key1)); + self::assertTrue($fake->has($key2)); + + $fake->remove($key1); + + self::assertFalse($fake->has($key1)); + self::assertTrue($fake->has($key2)); + } + #[Test] public function testHasReturnsExpectedResults(): void { @@ -106,6 +126,8 @@ public function __construct(private array $data) {} public function __invoke(): ConfigInterface { return new class($this->data) implements ConfigInterface { + use ArrayAccessConfigTrait; + public function __construct(private array $items) {} public function get(string $key, mixed $default = null): mixed @@ -129,6 +151,13 @@ public function set(array|ConfigInterface|string $key, mixed $value = null): voi } } + public function remove(string $key): void + { + if ($this->has($key)) { + unset($this->items[$key]); + } + } + public function toArray(): array { return $this->items; From 9c2b9788b0ce3c3439d8e7c416dc407998634a28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felipe=20Say=C3=A3o=20Lobato=20Abreu?= Date: Wed, 11 Jun 2025 11:18:25 -0300 Subject: [PATCH 2/4] Avoid test fails on array key different order --- tests/Helper/ConfigHelperTest.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/Helper/ConfigHelperTest.php b/tests/Helper/ConfigHelperTest.php index 9d8eab6..e37db78 100644 --- a/tests/Helper/ConfigHelperTest.php +++ b/tests/Helper/ConfigHelperTest.php @@ -72,7 +72,7 @@ public function testNormalizeWillConvertDotNotationToNestedArray(): void $result = ConfigHelper::normalize($input); - self::assertSame($expected, $result); + self::assertEquals($expected, $result); } #[Test] @@ -92,7 +92,7 @@ public function testNormalizeWillHandleMixedNestedArrays(): void $result = ConfigHelper::normalize($input); - self::assertSame($expected, $result); + self::assertEquals($expected, $result); } #[Test] @@ -102,7 +102,7 @@ public function testNormalizeWillReturnIndexedArrayUnchanged(): void $result = ConfigHelper::normalize($input); - self::assertSame($input, $result); + self::assertEquals($input, $result); } #[Test] @@ -124,7 +124,7 @@ public function testNormalizeWillMergeArraysWhenKeysOverlap(): void $result = ConfigHelper::normalize($input); - self::assertSame($expected, $result); + self::assertEquals($expected, $result); } #[Test] @@ -148,7 +148,7 @@ public function testFlattenWillConvertNestedArrayToDotNotation(): void $result = iterator_to_array(ConfigHelper::flatten($input)); - self::assertSame($expected, $result); + self::assertEquals($expected, $result); } #[Test] @@ -158,6 +158,6 @@ public function testFlattenWillHandleEmptyArray(): void $result = iterator_to_array(ConfigHelper::flatten($input)); - self::assertSame([], $result); + self::assertEquals([], $result); } } From 34aea05055ab5df9c9bdfdb2172d027d08073bb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felipe=20Say=C3=A3o=20Lobato=20Abreu?= Date: Wed, 11 Jun 2025 11:22:07 -0300 Subject: [PATCH 3/4] Remove warnings --- tests/FunctionsTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/FunctionsTest.php b/tests/FunctionsTest.php index edaf56f..05a16af 100644 --- a/tests/FunctionsTest.php +++ b/tests/FunctionsTest.php @@ -52,7 +52,6 @@ #[UsesClass(DirectoryConfig::class)] #[UsesClass(RecursiveDirectoryConfig::class)] #[UsesClass(LamiasConfigAggregatorConfig::class)] -#[UsesClass(LazyLoadConfigTrait::class)] final class FunctionsTest extends TestCase { use ProphecyTrait; From d2efe6c802292cf9b06881001c2d5c046692f940 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felipe=20Say=C3=A3o=20Lobato=20Abreu?= Date: Wed, 11 Jun 2025 11:32:03 -0300 Subject: [PATCH 4/4] Remove warnings --- tests/ArrayAccessConfigTraitTest.php | 4 ++-- tests/FunctionsTest.php | 2 ++ tests/LazyLoadConfigTraitTest.php | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/ArrayAccessConfigTraitTest.php b/tests/ArrayAccessConfigTraitTest.php index dbb3a76..3867d6b 100644 --- a/tests/ArrayAccessConfigTraitTest.php +++ b/tests/ArrayAccessConfigTraitTest.php @@ -14,14 +14,14 @@ */ use FastForward\Config\ArrayAccessConfigTrait; -use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\CoversTrait; use PHPUnit\Framework\TestCase; use Prophecy\PhpUnit\ProphecyTrait; /** * @internal */ -#[CoversClass(ArrayAccessConfigTrait::class)] +#[CoversTrait(ArrayAccessConfigTrait::class)] final class ArrayAccessConfigTraitTest extends TestCase { use ProphecyTrait; diff --git a/tests/FunctionsTest.php b/tests/FunctionsTest.php index 05a16af..2db1efe 100644 --- a/tests/FunctionsTest.php +++ b/tests/FunctionsTest.php @@ -28,6 +28,7 @@ use PHPUnit\Framework\Attributes\CoversFunction; use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\Attributes\UsesClass; +use PHPUnit\Framework\Attributes\UsesTrait; use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; @@ -52,6 +53,7 @@ #[UsesClass(DirectoryConfig::class)] #[UsesClass(RecursiveDirectoryConfig::class)] #[UsesClass(LamiasConfigAggregatorConfig::class)] +#[UsesTrait(LazyLoadConfigTrait::class)] final class FunctionsTest extends TestCase { use ProphecyTrait; diff --git a/tests/LazyLoadConfigTraitTest.php b/tests/LazyLoadConfigTraitTest.php index 333f44a..febb3ff 100644 --- a/tests/LazyLoadConfigTraitTest.php +++ b/tests/LazyLoadConfigTraitTest.php @@ -18,7 +18,7 @@ use FastForward\Config\ArrayAccessConfigTrait; use FastForward\Config\ConfigInterface; use FastForward\Config\LazyLoadConfigTrait; -use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\CoversTrait; use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; use Prophecy\PhpUnit\ProphecyTrait; @@ -26,7 +26,7 @@ /** * @internal */ -#[CoversClass(LazyLoadConfigTrait::class)] +#[CoversTrait(LazyLoadConfigTrait::class)] final class LazyLoadConfigTraitTest extends TestCase { use ProphecyTrait;