Skip to content

Commit

Permalink
feat: Add support for covers class method annotation
Browse files Browse the repository at this point in the history
  • Loading branch information
dshafik committed Sep 12, 2024
1 parent 0d50d35 commit e1146c4
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 12 deletions.
2 changes: 1 addition & 1 deletion src/Factories/Attribute.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
final class Attribute
{
/**
* @param iterable<int, string> $arguments
* @param iterable<int|class-string, string> $arguments
*/
public function __construct(public string $name, public iterable $arguments)
{
Expand Down
52 changes: 43 additions & 9 deletions src/PendingCalls/TestCall.php
Original file line number Diff line number Diff line change
Expand Up @@ -513,28 +513,45 @@ public function note(array|string $note): self
/**
* Sets the covered classes or methods.
*
* @param array<int, string>|string $classesOrFunctions
* @param array<int, string|array<class-string, string>>|string|array<class-string, string> ...$classesOrFunctions
*/
public function covers(array|string ...$classesOrFunctions): self
{
/** @var array<int, string> $classesOrFunctions */
/** @var array<int, string|array<int, string>>|array<class-string, string> $classesOrFunctions */
$classesOrFunctions = array_reduce($classesOrFunctions, fn ($carry, $item): array => is_array($item) ? array_merge($carry, $item) : array_merge($carry, [$item]), []); // @pest-ignore-type

foreach ($classesOrFunctions as $classOrFunction) {
$isClass = class_exists($classOrFunction) || interface_exists($classOrFunction) || enum_exists($classOrFunction);
$isTrait = trait_exists($classOrFunction);
$isFunction = function_exists($classOrFunction);
/** @var array<int, string> $classesOrFunctions */
if (count($classesOrFunctions) === 2 && \method_exists($classesOrFunctions[0], $classesOrFunctions[1])) {
$classesOrFunctions = [$classesOrFunctions];
}

if (! $isClass && ! $isTrait && ! $isFunction) {
throw new InvalidArgumentException(sprintf('No class, trait or method named "%s" has been found.', $classOrFunction));
foreach ($classesOrFunctions as $classOrFunction) {
$isClassMethod = is_array($classOrFunction) && method_exists($classOrFunction[0], $classOrFunction[1]) && class_exists($classOrFunction[0]);
$isClass = ! is_array($classOrFunction) && (class_exists($classOrFunction) || interface_exists($classOrFunction) || enum_exists($classOrFunction));
$isTrait = ! is_array($classOrFunction) && trait_exists($classOrFunction);
$isFunction = ! is_array($classOrFunction) && function_exists($classOrFunction);

if (! $isClass && ! $isTrait && ! $isFunction && ! $isClassMethod) {
throw new InvalidArgumentException(
sprintf(
'No class, method, trait or function named "%s" has been found.',
is_array($classOrFunction) ? $classOrFunction[0].'::'.$classOrFunction[1] : $classOrFunction
)
);
}

if ($isClass) {
/** @var string $classOrFunction */
$this->coversClass($classOrFunction);
} elseif ($isTrait) {
/** @var string $classOrFunction */
$this->coversTrait($classOrFunction);
} else {
} elseif ($isFunction) {
/** @var string $classOrFunction */
$this->coversFunction($classOrFunction);
} elseif ($isClassMethod) {
/** @var array<class-string, string> $classOrFunction */
$this->coversMethod($classOrFunction);
}
}

Expand Down Expand Up @@ -587,6 +604,23 @@ public function coversTrait(string ...$traits): self
return $this;
}

/**
* Sets the covered methods.
*
* @param array<class-string, string> ...$methods
*/
public function coversMethod(array ...$methods): self
{
foreach ($methods as $method) {
$this->testCaseFactoryAttributes[] = new Attribute(
\PHPUnit\Framework\Attributes\CoversMethod::class,
$method,
);
}

return $this;
}

/**
* Sets the covered functions.
*/
Expand Down
26 changes: 24 additions & 2 deletions tests/Features/Covers.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,29 @@ function testCoversFunction() {}
})->coversNothing();

it('throws exception if no class nor method has been found', function () {
$testCall = new TestCall(TestSuite::getInstance(), 'filename', 'description', fn () => 'closure');
$testCall = new TestCall(TestSuite::getInstance(), 'filename', 'no class nor method has been found', fn () => 'closure');

$testCall->covers('fakeName');
})->throws(InvalidArgumentException::class, 'No class, trait or method named "fakeName" has been found.');
})->throws(InvalidArgumentException::class, 'No class, method, trait or function named "fakeName" has been found.');

it('uses the correct PHPUnit attribute for class method', function () {
$attributes = (new ReflectionClass($this))->getAttributes();

expect($attributes[12]->getName())->toBe('PHPUnit\Framework\Attributes\CoversMethod');
expect($attributes[12]->getArguments()[0])->toBe('Tests\Fixtures\Covers\CoversClass1');
expect($attributes[12]->getArguments()[1])->toBe('foo');
})->covers([[CoversClass1::class, 'foo']]);

it('uses the correct PHPUnit attribute for single class method', function () {
$attributes = (new ReflectionClass($this))->getAttributes();

expect($attributes[12]->getName())->toBe('PHPUnit\Framework\Attributes\CoversMethod');
expect($attributes[12]->getArguments()[0])->toBe('Tests\Fixtures\Covers\CoversClass1');
expect($attributes[12]->getArguments()[1])->toBe('foo');
})->covers([CoversClass1::class, 'foo']);

it('throws exception if no class method has been found', function () {
$testCall = new TestCall(TestSuite::getInstance(), 'filename', 'no class method has been found', fn () => 'closure');

$testCall->covers([['fakeClass', 'fakeMethod']]);
})->throws(InvalidArgumentException::class, 'No class, method, trait or function named "fakeClass::fakeMethod" has been found.');

0 comments on commit e1146c4

Please sign in to comment.