diff --git a/src/Factories/Attributes/Covers.php b/src/Factories/Attributes/Covers.php new file mode 100644 index 00000000..2c6234f3 --- /dev/null +++ b/src/Factories/Attributes/Covers.php @@ -0,0 +1,35 @@ + $attributes + * + * @return array + */ + public function __invoke(TestCaseMethodFactory $method, array $attributes): array + { + foreach ($method->covers as $covering) { + if (is_array($covering)) { + $attributes[] = "#[\PHPUnit\Framework\Attributes\CoversClass({$covering[0]}]"; + $attributes[] = "#[\PHPUnit\Framework\Attributes\CoversFunction({$covering[1]}]"; + } else { + $attributes[] = "#[\PHPUnit\Framework\Attributes\CoversClass($covering)]"; + } + } + + return $attributes; + } +} diff --git a/src/Factories/TestCaseMethodFactory.php b/src/Factories/TestCaseMethodFactory.php index 987a2d27..2c029162 100644 --- a/src/Factories/TestCaseMethodFactory.php +++ b/src/Factories/TestCaseMethodFactory.php @@ -47,6 +47,13 @@ final class TestCaseMethodFactory */ public array $groups = []; + /** + * The covered classes and methods, if any. + * + * @var array + */ + public array $covers = []; + /** * Creates a new Factory instance. */ @@ -108,7 +115,7 @@ final class TestCaseMethodFactory * * @param array $annotationsToUse */ - public function buildForEvaluation(string $classFQN, array $annotationsToUse): string + public function buildForEvaluation(string $classFQN, array $annotationsToUse, array $attributesToUse): string { if ($this->description === null) { throw ShouldNotHappen::fromMessage('The test description may not be empty.'); @@ -122,12 +129,18 @@ final class TestCaseMethodFactory $datasetsCode = ''; $annotations = ['@test']; + $attributes = []; foreach ($annotationsToUse as $annotation) { /** @phpstan-ignore-next-line */ $annotations = (new $annotation())->__invoke($this, $annotations); } + foreach ($attributesToUse as $attribute) { + /** @phpstan-ignore-next-line */ + $attributes = (new $attribute())->__invoke($this, $attributes); + } + if (count($this->datasets) > 0) { $dataProviderName = $methodName . '_dataset'; $annotations[] = "@dataProvider $dataProviderName"; @@ -138,10 +151,15 @@ final class TestCaseMethodFactory static fn ($annotation) => sprintf("\n * %s", $annotation), $annotations, )); + $attributes = implode('', array_map( + static fn ($attribute) => sprintf("\n %s", $attribute), $attributes, + )); + return <<__runTest( diff --git a/src/PendingCalls/TestCall.php b/src/PendingCalls/TestCall.php index f9e786c1..53a0cd2f 100644 --- a/src/PendingCalls/TestCall.php +++ b/src/PendingCalls/TestCall.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Pest\PendingCalls; use Closure; +use InvalidArgumentException; use Pest\Factories\TestCaseMethodFactory; use Pest\Support\Backtrace; use Pest\Support\HigherOrderCallables; @@ -168,6 +169,25 @@ final class TestCall return $this; } + /** + * Sets the covered class and method. + */ + public function covers(string|array ...$classes): TestCall + { + foreach ($classes as $i => $class) { + if (is_array($class) && count($class) !== 2) { + throw new InvalidArgumentException(sprintf( + 'The #%s covered class must be an array with exactly 2 items: class and method name.', + $i + )); + } + + $this->testCaseMethod->covers[] = $class; + } + + return $this; + } + /** * Saves the property accessors to be used on the target. */