Skip to content

Commit

Permalink
[TesterBundle] phpunit test injection
Browse files Browse the repository at this point in the history
  • Loading branch information
mpoiriert committed Feb 17, 2024
1 parent 3a00ee2 commit 9dee0e3
Show file tree
Hide file tree
Showing 10 changed files with 201 additions and 18 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace Draw\Bundle\TesterBundle\PHPStan\Rules\Properties;

use Draw\Bundle\TesterBundle\PHPUnit\Extension\SetUpAutowire\AutowireService;
use PHPStan\Reflection\PropertyReflection;
use PHPStan\Rules\Properties\ReadWritePropertiesExtension;

class AutowireReadWritePropertiesExtension implements ReadWritePropertiesExtension
{
public function isAlwaysRead(PropertyReflection $property, string $propertyName): bool
{
return false;
}

public function isAlwaysWritten(PropertyReflection $property, string $propertyName): bool
{
return $this->hasProperAttribute($property, $propertyName, AutowireService::class);
}

public function isInitialized(PropertyReflection $property, string $propertyName): bool
{
return $this->hasProperAttribute($property, $propertyName, AutowireService::class);
}

private function hasProperAttribute(PropertyReflection $property, string $propertyName, string $attribute): bool
{
$properReflection = $property->getDeclaringClass()->getNativeProperty($propertyName)->getNativeReflection();

return 0 !== \count($properReflection->getAttributes($attribute, \ReflectionAttribute::IS_INSTANCEOF));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

namespace Draw\Bundle\TesterBundle\PHPUnit\Extension\SetUpAutowire;

interface AutowireInterface
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace Draw\Bundle\TesterBundle\PHPUnit\Extension\SetUpAutowire;

#[\Attribute(\Attribute::TARGET_PROPERTY)]
class AutowireService
{
public function __construct(private ?string $serviceId = null)
{
}

public function getServiceId(): ?string
{
return $this->serviceId;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

namespace Draw\Bundle\TesterBundle\PHPUnit\Extension\SetUpAutowire;

#[\Attribute(\Attribute::TARGET_PROPERTY)]
class AutowireTransportTester extends AutowireService
{
public function __construct(string $tranportName)
{
parent::__construct(sprintf('messenger.transport.%s.draw.tester', $tranportName));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
<?php

namespace Draw\Bundle\TesterBundle\PHPUnit\Extension\SetUpAutowire;

use Draw\Component\Core\Reflection\ReflectionAccessor;
use Draw\Component\Core\Reflection\ReflectionExtractor;
use PHPUnit\Event\Code\TestMethod;
use PHPUnit\Event\Test\Prepared as TestPrepared;
use PHPUnit\Event\Test\PreparedSubscriber as TestPreparedSubscriber;
use PHPUnit\Framework\TestCase;
use PHPUnit\Runner\Extension\Extension;
use PHPUnit\Runner\Extension\Facade;
use PHPUnit\Runner\Extension\ParameterCollection;
use PHPUnit\TextUI\Configuration\Configuration;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;

class SetUpAutowireExtension implements Extension
{
public function bootstrap(Configuration $configuration, Facade $facade, ParameterCollection $parameters): void
{
$facade->registerSubscribers(
new class() implements TestPreparedSubscriber {
/**
* @var array<string, array<int, array{\ReflectionProperty, string}>>
*/
private array $propertyAttributes = [];

public function notify(TestPrepared $event): void
{
$test = $event->test();

\assert($test instanceof TestMethod);

if (!is_a($test->className(), AutowireInterface::class, true)) {
return;
}

$testCase = null;

foreach (debug_backtrace() as $frame) {
if (isset($frame['object']) && $frame['object'] instanceof TestCase) {
$testCase = $frame['object'];
break;
}
}

if (!$testCase instanceof AutowireInterface) {
return;
}

if (!$testCase instanceof KernelTestCase) {
return;
}

$container = null;

foreach ($this->getPropertyAttributes($testCase) as [$property, $serviceId]) {
$container ??= ReflectionAccessor::callMethod($testCase, 'getContainer');

$property->setValue(
$testCase,
$container->get($serviceId)
);
}
}

/**
* @return iterable<array{0:\ReflectionProperty, 1: string}>
*/
private function getPropertyAttributes(TestCase $testCase): iterable
{
$className = $testCase::class;

if (!\array_key_exists($className, $this->propertyAttributes)) {
$this->propertyAttributes[$className] = [];

foreach ((new \ReflectionObject($testCase))->getProperties() as $property) {
$attribute = $property->getAttributes(AutowireService::class, \ReflectionAttribute::IS_INSTANCEOF)[0] ?? null;

if (!$attribute) {
continue;
}

$autoWireService = $attribute->newInstance();

$serviceId = $autoWireService->getServiceId();

if (!$serviceId) {
$classes = ReflectionExtractor::getClasses($property->getType());
if (1 !== \count($classes)) {
throw new \RuntimeException('Property '.$property->getName().' of class '.$testCase::class.' must have a type hint.');
}

$serviceId = $classes[0];
}

$this->propertyAttributes[$className][] = [$property, $serviceId];
}
}

foreach ($this->propertyAttributes[$className] as $property) {
yield $property;
}
}
},
);
}
}
5 changes: 5 additions & 0 deletions packages/tester-bundle/extension.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
services:
draw.tester_bundle.autowire_read_write_properties_extension:
class: Draw\Bundle\TesterBundle\PHPStan\Rules\Properties\AutowireReadWritePropertiesExtension
tags:
- phpstan.properties.readWriteExtension
1 change: 1 addition & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
includes:
- phpstan-baseline.neon
- packages/tester-bundle/extension.neon

parameters:
level: 5
Expand Down
1 change: 1 addition & 0 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,6 @@
<extensions>
<bootstrap class="Draw\Component\Tester\PHPUnit\Extension\CarbonResetExtension"/>
<bootstrap class="Draw\Bundle\TesterBundle\PHPUnit\Extension\KernelShutdownExtension"/>
<bootstrap class="Draw\Bundle\TesterBundle\PHPUnit\Extension\SetUpAutowire\SetUpAutowireExtension"/>
</extensions>
</phpunit>
27 changes: 12 additions & 15 deletions tests/Entity/UserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,32 @@
use App\Tests\TestCase;
use Doctrine\ORM\EntityManagerInterface;
use Draw\Bundle\TesterBundle\Messenger\TransportTester;
use Draw\Bundle\TesterBundle\PHPUnit\Extension\SetUpAutowire\AutowireInterface;
use Draw\Bundle\TesterBundle\PHPUnit\Extension\SetUpAutowire\AutowireService;
use Draw\Bundle\TesterBundle\PHPUnit\Extension\SetUpAutowire\AutowireTransportTester;
use Draw\Bundle\UserBundle\Entity\UserLock;
use Draw\Bundle\UserBundle\Message\NewUserLockMessage;
use Draw\Bundle\UserBundle\Message\UserLockDelayedActivationMessage;
use Draw\Component\Messenger\Searchable\Stamp\SearchableTagStamp;
use PHPUnit\Framework\Attributes\After;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Messenger\Stamp\ReceivedStamp;

class UserTest extends TestCase
class UserTest extends TestCase implements AutowireInterface
{
#[AutowireService]
private EntityManagerInterface $entityManager;

#[AutowireTransportTester('sync')]
private TransportTester $transportTester;

protected function setUp(): void
{
$this->entityManager = static::getContainer()->get(EntityManagerInterface::class);
$this->transportTester = static::getTransportTester('sync');
}
#[AutowireService]
private MessageBusInterface $messageBus;

/**
* @before
*
* @after
*/
#[After]
public function cleanUp(): void
{
static::getService(EntityManagerInterface::class)
$this->entityManager
->createQueryBuilder()
->delete(User::class, 'user')
->where('user.email = :email')
Expand Down Expand Up @@ -72,9 +71,7 @@ public function testLockDelayed(): void
static::assertInstanceOf(NewUserLockMessage::class, $envelope->getMessage());

$this->transportTester->reset();
static::getContainer()
->get(MessageBusInterface::class)
->dispatch($envelope->with(new ReceivedStamp('sync')));
$this->messageBus->dispatch($envelope->with(new ReceivedStamp('sync')));

$this->transportTester->assertMessageMatch(UserLockDelayedActivationMessage::class);

Expand Down
10 changes: 7 additions & 3 deletions tests/Validator/Constraints/ValueIsNotUsedValidatorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,23 @@

use App\Entity\Tag;
use App\Entity\User;
use Draw\Bundle\TesterBundle\PHPUnit\Extension\SetUpAutowire\AutowireInterface;
use Draw\Bundle\TesterBundle\PHPUnit\Extension\SetUpAutowire\AutowireService;
use Draw\Component\Validator\Constraints\ValueIsNotUsed;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Validator\Validator\ValidatorInterface;

class ValueIsNotUsedValidatorTest extends KernelTestCase
class ValueIsNotUsedValidatorTest extends KernelTestCase implements AutowireInterface
{
#[AutowireService]
protected ValidatorInterface $validator;

/**
* @dataProvider provideTestValidate
*/
public function testValidate(mixed $value, string $entityClass, string $field, bool $expectError): void
{
$violations = static::getContainer()
->get(ValidatorInterface::class)
$violations = $this->validator
->validate($value, new ValueIsNotUsed(entityClass: $entityClass, field: $field));

if (!$expectError) {
Expand Down

0 comments on commit 9dee0e3

Please sign in to comment.