From cb8b148d56617557c5d11b4ea6509f40f73d5543 Mon Sep 17 00:00:00 2001 From: Tom H Anderson Date: Mon, 28 Oct 2024 19:06:55 -0600 Subject: [PATCH] Installed testbench; removed problematic InteractsWithEntities trait and test --- composer.json | 27 ++++- src/DoctrineServiceProvider.php | 60 +---------- .../Concerns/InteractsWithEntities.php | 102 ------------------ testbench.yaml | 20 ++++ .../Configuration/Cache/CacheManagerTest.php | 14 +-- .../Cache/FileCacheProviderTest.php | 5 +- .../Cache/PhpFileCacheProviderTest.php | 5 +- .../Connections/ConnectionManagerTest.php | 12 +-- .../MetaData/MetaDataManagerTest.php | 10 +- .../Concerns/InteractsWithEntitiesTest.php | 100 ----------------- tests/TestCase.php | 17 +-- workbench/app/Models/.gitkeep | 0 workbench/app/Models/User.php | 45 ++++++++ .../Providers/WorkbenchServiceProvider.php | 24 +++++ workbench/bootstrap/app.php | 19 ++++ workbench/bootstrap/providers.php | 5 + workbench/database/factories/.gitkeep | 0 workbench/database/factories/UserFactory.php | 54 ++++++++++ workbench/database/migrations/.gitkeep | 0 workbench/database/seeders/DatabaseSeeder.php | 23 ++++ workbench/resources/views/.gitkeep | 0 workbench/routes/console.php | 8 ++ workbench/routes/web.php | 7 ++ 23 files changed, 263 insertions(+), 294 deletions(-) delete mode 100644 src/Testing/Concerns/InteractsWithEntities.php create mode 100644 testbench.yaml delete mode 100644 tests/Feature/Testing/Concerns/InteractsWithEntitiesTest.php create mode 100644 workbench/app/Models/.gitkeep create mode 100644 workbench/app/Models/User.php create mode 100644 workbench/app/Providers/WorkbenchServiceProvider.php create mode 100644 workbench/bootstrap/app.php create mode 100644 workbench/bootstrap/providers.php create mode 100644 workbench/database/factories/.gitkeep create mode 100644 workbench/database/factories/UserFactory.php create mode 100644 workbench/database/migrations/.gitkeep create mode 100644 workbench/database/seeders/DatabaseSeeder.php create mode 100644 workbench/resources/views/.gitkeep create mode 100644 workbench/routes/console.php create mode 100644 workbench/routes/web.php diff --git a/composer.json b/composer.json index 3f20219e..c3fc37e9 100644 --- a/composer.json +++ b/composer.json @@ -46,7 +46,8 @@ "phpstan/phpstan-deprecation-rules": "^1.1", "phpunit/phpunit": "^11.4", "fakerphp/faker": "^1.23", - "laravel/framework": "^10.0 || ^11.0" + "laravel/framework": "^10.0 || ^11.0", + "orchestra/testbench": "^9.5" }, "conflict": { "laravel/lumen": "*" @@ -61,7 +62,10 @@ }, "autoload-dev": { "psr-4": { - "LaravelDoctrineTest\\ORM\\": "tests/" + "LaravelDoctrineTest\\ORM\\": "tests/", + "Workbench\\App\\": "workbench/app/", + "Workbench\\Database\\Factories\\": "workbench/database/factories/", + "Workbench\\Database\\Seeders\\": "workbench/database/seeders/" } }, "suggest": { @@ -95,6 +99,21 @@ "vendor/bin/phpunit", "vendor/bin/phpstan analyze src --level 1" ], - "coverage": "XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-html=coverage" + "coverage": "XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-html=coverage", + "post-autoload-dump": [ + "@clear", + "@prepare" + ], + "clear": "@php vendor/bin/testbench package:purge-skeleton --ansi", + "prepare": "@php vendor/bin/testbench package:discover --ansi", + "build": "@php vendor/bin/testbench workbench:build --ansi", + "serve": [ + "Composer\\Config::disableProcessTimeout", + "@build", + "@php vendor/bin/testbench serve --ansi" + ], + "lint": [ + "@php vendor/bin/phpstan analyse --verbose --ansi" + ] } -} +} \ No newline at end of file diff --git a/src/DoctrineServiceProvider.php b/src/DoctrineServiceProvider.php index 69dfbcad..d6ef931b 100644 --- a/src/DoctrineServiceProvider.php +++ b/src/DoctrineServiceProvider.php @@ -10,10 +10,8 @@ use Doctrine\ORM\Proxy\Autoloader; use Doctrine\Persistence\ManagerRegistry; use Illuminate\Contracts\Container\Container; -use Illuminate\Contracts\Validation\Factory as ValidationFactory; use Illuminate\Notifications\ChannelManager; use Illuminate\Support\ServiceProvider; -use Illuminate\Support\Str; use InvalidArgumentException; use LaravelDoctrine\ORM\Auth\DoctrineUserProvider; use LaravelDoctrine\ORM\Configuration\Cache\CacheManager; @@ -38,7 +36,6 @@ use function assert; use function class_exists; use function config_path; -use function property_exists; class DoctrineServiceProvider extends ServiceProvider { @@ -50,13 +47,9 @@ public function boot(): void $this->extendAuthManager(); $this->extendNotificationChannel(); - if (! $this->isLumen()) { - $this->publishes([ - $this->getConfigPath() => config_path('doctrine.php'), - ], 'config'); - } - - $this->ensureValidatorIsUsable(); + $this->publishes([ + $this->getConfigPath() => config_path('doctrine.php'), + ], 'config'); } /** @@ -83,28 +76,6 @@ public function register(): void $this->registerPresenceVerifierProvider(); } - protected function ensureValidatorIsUsable(): void - { - if (! $this->isLumen()) { - return; - } - - assert(property_exists($this->app, 'availableBindings')); - - if ($this->shouldRegisterDoctrinePresenceValidator()) { - // due to weirdness the default presence verifier overrides one set by a service provider - // so remove them so we can re add our implementation later - unset($this->app->availableBindings['validator']); - unset($this->app->availableBindings[ValidationFactory::class]); - } else { - // resolve the db, - // this makes `isset($this->app['db']) == true` - // which is required to set the presence verifier - // in the default ValidationServiceProvider implementation - $this->app['db']; - } - } - /** * Merge config */ @@ -114,14 +85,6 @@ protected function mergeConfig(): void $this->getConfigPath(), 'doctrine', ); - - if (! $this->isLumen()) { - return; - } - - $this->app->configure('cache'); - $this->app->configure('database'); - $this->app->configure('doctrine'); } /** @@ -232,15 +195,7 @@ protected function registerExtensions(): void */ protected function registerPresenceVerifierProvider(): void { - if ($this->isLumen()) { - $this->app->singleton('validator', function () { - $this->app->register(PresenceVerifierProvider::class); - - return $this->app->make('validator'); - }); - } else { - $this->app->register(PresenceVerifierProvider::class); - } + $this->app->register(PresenceVerifierProvider::class); } /** @@ -279,7 +234,7 @@ protected function extendAuthManager(): void /** * Boots the extension manager at the appropriate time depending on if the app - * is running as Laravel HTTP, Lumen HTTP or in a console environment + * is running as Laravel HTTP or in a console environment */ protected function bootExtensionManager(): void { @@ -354,11 +309,6 @@ protected function registerConsoleCommands(): void ]); } - protected function isLumen(): bool - { - return Str::contains($this->app->version(), 'Lumen'); - } - protected function shouldRegisterDoctrinePresenceValidator(): bool { return $this->app['config']->get('doctrine.doctrine_presence_verifier', true); diff --git a/src/Testing/Concerns/InteractsWithEntities.php b/src/Testing/Concerns/InteractsWithEntities.php deleted file mode 100644 index ae980130..00000000 --- a/src/Testing/Concerns/InteractsWithEntities.php +++ /dev/null @@ -1,102 +0,0 @@ -entityManager()->find($class, $id); - - Assert::assertNotNull($entity, 'A [' . $class . '] entity was not found by id: ' . print_r($id, true)); - - return $entity; - } - - public function entityDoesNotExist(string $class, mixed $id): void - { - Assert::assertNull( - $this->entityManager()->find($class, $id), - 'A [' . $class . '] entity was found by id: ' . print_r($id, true), - ); - } - - /** - * @param mixed[] $criteria - * - * @return mixed[] - */ - public function entitiesMatch(string $class, array $criteria, int|null $count = null): mixed - { - $entities = $this->entityManager()->getRepository($class)->findBy($criteria); - - Assert::assertNotEmpty($entities, 'No [' . $class . '] entities were found with the given criteria: ' . $this->outputCriteria($criteria)); - - if ($count !== null) { - Assert::assertCount( - $count, - $entities, - 'Expected to find ' . $count . ' [' . $class . '] entities, but found ' . count($entities) . - ' with the given criteria: ' . $this->outputCriteria($criteria), - ); - } - - return $entities; - } - - /** @param mixed[] $criteria */ - public function noEntitiesMatch(string $class, array $criteria): void - { - Assert::assertEmpty( - $this->entityManager()->getRepository($class)->findBy($criteria), - 'Some [' . $class . '] entities were found with the given criteria: ' . $this->outputCriteria($criteria), - ); - } - - /** - * Replaces entities with their ids in the criteria array and print_r them - * - * @param mixed[] $criteria - */ - private function outputCriteria(array $criteria): string - { - $criteria = collect($criteria)->map(function ($value) { - if (! is_object($value)) { - return $value; - } - - $unityOfWork = $this->entityManager()->getUnitOfWork(); - if ($unityOfWork->isInIdentityMap($value)) { - return $unityOfWork->getEntityIdentifier($value); - } - - return $value; - })->all(); - - return print_r($criteria, true); - } - - protected function entityManager(): mixed - { - if (! isset($this->app)) { - Assert::markTestSkipped( - 'Tests that interact with entities through Doctrine need to have Laravel\'s Application object.' . PHP_EOL . - 'Please extend Laravel\'s TestCase to use this trait.', - ); - } - - return $this->app->make('em'); - } -} diff --git a/testbench.yaml b/testbench.yaml new file mode 100644 index 00000000..d5e9cf31 --- /dev/null +++ b/testbench.yaml @@ -0,0 +1,20 @@ +providers: + - LaravelDoctrine\ORM\DoctrineServiceProvider + +workbench: + start: '/' + install: true + health: false + discovers: + web: fale + api: false + commands: false + components: false + views: false + build: + - asset-publish + - create-sqlite-db + - db-wipe + assets: + - laravel-assets + sync: [] diff --git a/tests/Feature/Configuration/Cache/CacheManagerTest.php b/tests/Feature/Configuration/Cache/CacheManagerTest.php index 0cc64c33..7405dbbf 100644 --- a/tests/Feature/Configuration/Cache/CacheManagerTest.php +++ b/tests/Feature/Configuration/Cache/CacheManagerTest.php @@ -19,18 +19,18 @@ class CacheManagerTest extends TestCase { protected CacheManager $manager; - protected Container $app; + protected Container $testApp; protected Repository $config; protected function setUp(): void { - $this->app = m::mock(Container::class); - $this->app->shouldReceive('make')->andReturn(m::self()); - $this->app->shouldReceive('get')->with('doctrine.cache.default', 'array')->andReturn('array'); + $this->testApp = m::mock(Container::class); + $this->testApp->shouldReceive('make')->andReturn(m::self()); + $this->testApp->shouldReceive('get')->with('doctrine.cache.default', 'array')->andReturn('array'); $this->manager = new CacheManager( - $this->app, + $this->testApp, ); parent::setUp(); @@ -38,7 +38,7 @@ protected function setUp(): void public function testDriverReturnsTheDefaultDriver(): void { - $this->app->shouldReceive('resolve')->andReturn(new ArrayCacheProvider()); + $this->testApp->shouldReceive('resolve')->andReturn(new ArrayCacheProvider()); $this->assertInstanceOf(ArrayCacheProvider::class, $this->manager->driver()); $this->assertInstanceOf(ArrayAdapter::class, $this->manager->driver()->resolve()); @@ -49,7 +49,7 @@ public function testDriverCanReturnAGivenDriver(): void $config = m::mock(Repository::class); $app = m::mock(Application::class); - $this->app->shouldReceive('resolve')->andReturn(new FileCacheProvider( + $this->testApp->shouldReceive('resolve')->andReturn(new FileCacheProvider( $config, $app, )); diff --git a/tests/Feature/Configuration/Cache/FileCacheProviderTest.php b/tests/Feature/Configuration/Cache/FileCacheProviderTest.php index 248a9706..de844bf1 100644 --- a/tests/Feature/Configuration/Cache/FileCacheProviderTest.php +++ b/tests/Feature/Configuration/Cache/FileCacheProviderTest.php @@ -15,7 +15,10 @@ public function getProvider(): mixed { $config = m::mock(Repository::class); $config->shouldReceive('get') - ->with('cache.stores.file.path', '/storage/framework/cache') + ->with( + 'cache.stores.file.path', + $this->applicationBasePath() . '/storage/framework/cache', + ) ->once() ->andReturn('/tmp'); diff --git a/tests/Feature/Configuration/Cache/PhpFileCacheProviderTest.php b/tests/Feature/Configuration/Cache/PhpFileCacheProviderTest.php index ba0a0a65..bc305183 100644 --- a/tests/Feature/Configuration/Cache/PhpFileCacheProviderTest.php +++ b/tests/Feature/Configuration/Cache/PhpFileCacheProviderTest.php @@ -15,7 +15,10 @@ public function getProvider(): mixed { $config = m::mock(Repository::class); $config->shouldReceive('get') - ->with('cache.stores.file.path', '/storage/framework/cache') + ->with( + 'cache.stores.file.path', + $this->applicationBasePath() . '/storage/framework/cache', + ) ->once() ->andReturn('/tmp'); diff --git a/tests/Feature/Configuration/Connections/ConnectionManagerTest.php b/tests/Feature/Configuration/Connections/ConnectionManagerTest.php index 896c7dd3..332ee3c4 100644 --- a/tests/Feature/Configuration/Connections/ConnectionManagerTest.php +++ b/tests/Feature/Configuration/Connections/ConnectionManagerTest.php @@ -19,20 +19,20 @@ class ConnectionManagerTest extends TestCase { protected ConnectionManager $manager; - protected Container $app; + protected Container $testApp; protected Repository $config; protected function setUp(): void { - $this->app = m::mock(Container::class); - $this->app->shouldReceive('make')->andReturn(m::self()); + $this->testApp = m::mock(Container::class); + $this->testApp->shouldReceive('make')->andReturn(m::self()); $this->config = m::mock(Repository::class); $this->config->shouldReceive('get'); $this->manager = new ConnectionManager( - $this->app, + $this->testApp, ); parent::setUp(); @@ -40,7 +40,7 @@ protected function setUp(): void public function testDriverReturnsTheDefaultDriver(): void { - $this->app->shouldReceive('resolve')->andReturn( + $this->testApp->shouldReceive('resolve')->andReturn( (new MysqlConnection($this->config))->resolve(), ); @@ -50,7 +50,7 @@ public function testDriverReturnsTheDefaultDriver(): void public function testDriverCanReturnAGivenDriver(): void { - $this->app->shouldReceive('resolve')->andReturn( + $this->testApp->shouldReceive('resolve')->andReturn( (new SqliteConnection($this->config))->resolve(), ); diff --git a/tests/Feature/Configuration/MetaData/MetaDataManagerTest.php b/tests/Feature/Configuration/MetaData/MetaDataManagerTest.php index 11490e47..67fb2db0 100644 --- a/tests/Feature/Configuration/MetaData/MetaDataManagerTest.php +++ b/tests/Feature/Configuration/MetaData/MetaDataManagerTest.php @@ -15,15 +15,15 @@ class MetaDataManagerTest extends TestCase { protected MetaDataManager $manager; - protected Container $app; + protected Container $testApp; protected function setUp(): void { - $this->app = m::mock(Container::class); - $this->app->shouldReceive('make')->andReturn(m::self()); + $this->testApp = m::mock(Container::class); + $this->testApp->shouldReceive('make')->andReturn(m::self()); $this->manager = new MetaDataManager( - $this->app, + $this->testApp, ); parent::setUp(); @@ -31,7 +31,7 @@ protected function setUp(): void public function testDriverReturnsTheDefaultDriver(): void { - $this->app->shouldReceive('resolve')->andReturn(new XmlDriver('locator', '.xml')); + $this->testApp->shouldReceive('resolve')->andReturn(new XmlDriver('locator', '.xml')); $this->assertInstanceOf(XmlDriver::class, $this->manager->driver()); } diff --git a/tests/Feature/Testing/Concerns/InteractsWithEntitiesTest.php b/tests/Feature/Testing/Concerns/InteractsWithEntitiesTest.php deleted file mode 100644 index bb9371dd..00000000 --- a/tests/Feature/Testing/Concerns/InteractsWithEntitiesTest.php +++ /dev/null @@ -1,100 +0,0 @@ -em = Mockery::mock(EntityManagerInterface::class); - - $this->app = Mockery::mock(Container::class); - $this->app - ->allows('make') - ->with('em') - ->andReturn($this->em); - - parent::setUp(); - } - - public function testEntitiesMatchWithMatch(): void - { - $repository = Mockery::mock(EntityRepository::class); - $repository->expects('findBy') - ->with(['someField' => 'someValue']) - ->once() - ->andReturn(['entity']); - - $this->em->expects('getRepository') - ->with('SomeClass') - ->once() - ->andReturn($repository); - - $this->entitiesMatch('SomeClass', ['someField' => 'someValue']); - } - - public function testEntitiesMatchWithoutMatch(): void - { - $repository = Mockery::mock(EntityRepository::class); - $repository->expects('findBy') - ->with(['someField' => 'someValue']) - ->once() - ->andReturn([]); - - $this->em->expects('getRepository') - ->with('SomeClass') - ->once() - ->andReturn($repository); - - $this->expectException(ExpectationFailedException::class); - $this->entitiesMatch('SomeClass', ['someField' => 'someValue']); - } - - public function testNoEntitiesMatchWithMatch(): void - { - $repository = Mockery::mock(EntityRepository::class); - $repository->expects('findBy') - ->with(['someField' => 'someValue']) - ->once() - ->andReturn(['entity']); - - $this->em->expects('getRepository') - ->with('SomeClass') - ->once() - ->andReturn($repository); - - $this->expectException(ExpectationFailedException::class); - $this->noEntitiesMatch('SomeClass', ['someField' => 'someValue']); - } - - public function testNoEntitiesMatchWithoutMatch(): void - { - $repository = Mockery::mock(EntityRepository::class); - $repository->expects('findBy') - ->with(['someField' => 'someValue']) - ->once() - ->andReturn([]); - - $this->em->expects('getRepository') - ->with('SomeClass') - ->once() - ->andReturn($repository); - - $this->noEntitiesMatch('SomeClass', ['someField' => 'someValue']); - } -} diff --git a/tests/TestCase.php b/tests/TestCase.php index 315aaa79..c20aabf4 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -4,29 +4,20 @@ namespace LaravelDoctrineTest\ORM; -use Illuminate\Foundation\Application; -use PHPUnit\Framework\TestCase as PHPUnitTestCase; +use Orchestra\Testbench\Concerns\WithWorkbench; +use Orchestra\Testbench\TestCase as OrchestraTestCase; -class TestCase extends PHPUnitTestCase +class TestCase extends OrchestraTestCase { - private Application $application; + use WithWorkbench; protected function setUp(): void { - $this->application = new Application(); - parent::setUp(); } protected function tearDown(): void { - unset($this->application); - parent::tearDown(); } - - public function getApplication(): Application - { - return $this->application; - } } diff --git a/workbench/app/Models/.gitkeep b/workbench/app/Models/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/workbench/app/Models/User.php b/workbench/app/Models/User.php new file mode 100644 index 00000000..1405251f --- /dev/null +++ b/workbench/app/Models/User.php @@ -0,0 +1,45 @@ + + */ + protected $fillable = [ + 'name', + 'email', + 'password', + ]; + + /** + * The attributes that should be hidden for serialization. + * + * @var array + */ + protected $hidden = [ + 'password', + 'remember_token', + ]; + + /** + * The attributes that should be cast. + * + * @var array + */ + protected $casts = [ + 'email_verified_at' => 'datetime', + 'password' => 'hashed', + ]; +} diff --git a/workbench/app/Providers/WorkbenchServiceProvider.php b/workbench/app/Providers/WorkbenchServiceProvider.php new file mode 100644 index 00000000..e8cec9c2 --- /dev/null +++ b/workbench/app/Providers/WorkbenchServiceProvider.php @@ -0,0 +1,24 @@ +withRouting( + web: __DIR__.'/../routes/web.php', + commands: __DIR__.'/../routes/console.php', + ) + ->withMiddleware(function (Middleware $middleware) { + // + }) + ->withExceptions(function (Exceptions $exceptions) { + // + })->create(); diff --git a/workbench/bootstrap/providers.php b/workbench/bootstrap/providers.php new file mode 100644 index 00000000..3ac44ad1 --- /dev/null +++ b/workbench/bootstrap/providers.php @@ -0,0 +1,5 @@ + + */ +class UserFactory extends Factory +{ + /** + * The current password being used by the factory. + */ + protected static ?string $password; + + /** + * The name of the factory's corresponding model. + * + * @var class-string + */ + protected $model = User::class; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + 'name' => fake()->name(), + 'email' => fake()->unique()->safeEmail(), + 'email_verified_at' => now(), + 'password' => static::$password ??= Hash::make('password'), + 'remember_token' => Str::random(10), + ]; + } + + /** + * Indicate that the model's email address should be unverified. + */ + public function unverified(): static + { + return $this->state(fn (array $attributes) => [ + 'email_verified_at' => null, + ]); + } +} diff --git a/workbench/database/migrations/.gitkeep b/workbench/database/migrations/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/workbench/database/seeders/DatabaseSeeder.php b/workbench/database/seeders/DatabaseSeeder.php new file mode 100644 index 00000000..ce9bd159 --- /dev/null +++ b/workbench/database/seeders/DatabaseSeeder.php @@ -0,0 +1,23 @@ +create(); + + UserFactory::new()->create([ + 'name' => 'Test User', + 'email' => 'test@example.com', + ]); + } +} diff --git a/workbench/resources/views/.gitkeep b/workbench/resources/views/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/workbench/routes/console.php b/workbench/routes/console.php new file mode 100644 index 00000000..eff2ed24 --- /dev/null +++ b/workbench/routes/console.php @@ -0,0 +1,8 @@ +comment(Inspiring::quote()); +})->purpose('Display an inspiring quote')->hourly(); diff --git a/workbench/routes/web.php b/workbench/routes/web.php new file mode 100644 index 00000000..86a06c53 --- /dev/null +++ b/workbench/routes/web.php @@ -0,0 +1,7 @@ +