diff --git a/src/Framework/Attributes/TestWithArray.php b/src/Framework/Attributes/TestWithArray.php new file mode 100644 index 0000000000..0c31a92df8 --- /dev/null +++ b/src/Framework/Attributes/TestWithArray.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] +final readonly class TestWithArray +{ + /** + * @var array> + */ + private array $cases; + + /** + * @param array> $arrayOfCases + */ + public function __construct(array $arrayOfCases) + { + $this->cases = $arrayOfCases; + } + + /** + * @return array> + */ + public function arrayOfCases(): array + { + return $this->cases; + } +} diff --git a/src/Metadata/Parser/AttributeParser.php b/src/Metadata/Parser/AttributeParser.php index 7734db354a..87debfcca5 100644 --- a/src/Metadata/Parser/AttributeParser.php +++ b/src/Metadata/Parser/AttributeParser.php @@ -12,6 +12,8 @@ use const JSON_THROW_ON_ERROR; use function assert; use function class_exists; +use function is_array; +use function is_int; use function json_decode; use function method_exists; use function sprintf; @@ -74,6 +76,7 @@ use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\Attributes\TestDox; use PHPUnit\Framework\Attributes\TestWith; +use PHPUnit\Framework\Attributes\TestWithArray; use PHPUnit\Framework\Attributes\TestWithJson; use PHPUnit\Framework\Attributes\Ticket; use PHPUnit\Framework\Attributes\UsesClass; @@ -870,6 +873,16 @@ public function forMethod(string $className, string $methodName): MetadataCollec break; + case TestWithArray::class: + assert($attributeInstance instanceof TestWithArray); + + foreach ($attributeInstance->arrayOfCases() as $name => $case) { + assert(is_array($case)); + $result[] = Metadata::testWith($case, is_int($name) ? null : $name); + } + + break; + case Ticket::class: assert($attributeInstance instanceof Ticket); diff --git a/tests/_files/Metadata/Attribute/tests/TestWithTest.php b/tests/_files/Metadata/Attribute/tests/TestWithTest.php index 5ed7dd01f1..fe34931ead 100644 --- a/tests/_files/Metadata/Attribute/tests/TestWithTest.php +++ b/tests/_files/Metadata/Attribute/tests/TestWithTest.php @@ -10,6 +10,7 @@ namespace PHPUnit\TestFixture\Metadata\Attribute; use PHPUnit\Framework\Attributes\TestWith; +use PHPUnit\Framework\Attributes\TestWithArray; use PHPUnit\Framework\Attributes\TestWithJson; use PHPUnit\Framework\TestCase; @@ -38,4 +39,41 @@ public function testTwoWithName($one, $two, $three): void { $this->assertTrue(true); } + + #[TestWithArray([ + [1, true], + ])] + public function testTestWithArrayBasic($one, $two): void + { + $this->assertTrue(true); + } + + #[TestWithArray([ + 'firstCase' => [2, false], + ])] + public function testTestWithArrayNamedCase($one, $two): void + { + $this->assertTrue(true); + } + + #[TestWithArray([ + 'odds' => [1, 3], + [-1, 0], + 'evens' => [2, 4], + ])] + public function testTestWithArrayManyCasesWithMixedNames($one, $two): void + { + $this->assertTrue(true); + } + + #[TestWithArray([ + [5], + ])] + #[TestWithArray([ + [6], + ])] + public function testMultipleTestWithArray($one): void + { + $this->assertTrue(true); + } } diff --git a/tests/end-to-end/event/testwith-attribute.phpt b/tests/end-to-end/event/testwith-attribute.phpt index d5761afdc7..4c0c203d97 100644 --- a/tests/end-to-end/event/testwith-attribute.phpt +++ b/tests/end-to-end/event/testwith-attribute.phpt @@ -14,11 +14,11 @@ require __DIR__ . '/../../bootstrap.php'; PHPUnit Started (PHPUnit %s using %s) Test Runner Configured Event Facade Sealed -Test Suite Loaded (4 tests) +Test Suite Loaded (11 tests) Test Runner Started Test Suite Sorted -Test Runner Execution Started (4 tests) -Test Suite Started (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest, 4 tests) +Test Runner Execution Started (11 tests) +Test Suite Started (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest, 11 tests) Test Suite Started (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testOne, 1 test) Test Preparation Started (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testOne#0) Test Prepared (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testOne#0) @@ -43,7 +43,43 @@ Test Prepared (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testTwoWithN Test Passed (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testTwoWithName#Name2) Test Finished (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testTwoWithName#Name2) Test Suite Finished (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testTwoWithName, 1 test) -Test Suite Finished (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest, 4 tests) +Test Suite Started (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testTestWithArrayBasic, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testTestWithArrayBasic#0) +Test Prepared (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testTestWithArrayBasic#0) +Test Passed (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testTestWithArrayBasic#0) +Test Finished (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testTestWithArrayBasic#0) +Test Suite Finished (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testTestWithArrayBasic, 1 test) +Test Suite Started (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testTestWithArrayNamedCase, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testTestWithArrayNamedCase#firstCase) +Test Prepared (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testTestWithArrayNamedCase#firstCase) +Test Passed (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testTestWithArrayNamedCase#firstCase) +Test Finished (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testTestWithArrayNamedCase#firstCase) +Test Suite Finished (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testTestWithArrayNamedCase, 1 test) +Test Suite Started (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testTestWithArrayManyCasesWithMixedNames, 3 tests) +Test Preparation Started (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testTestWithArrayManyCasesWithMixedNames#odds) +Test Prepared (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testTestWithArrayManyCasesWithMixedNames#odds) +Test Passed (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testTestWithArrayManyCasesWithMixedNames#odds) +Test Finished (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testTestWithArrayManyCasesWithMixedNames#odds) +Test Preparation Started (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testTestWithArrayManyCasesWithMixedNames#0) +Test Prepared (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testTestWithArrayManyCasesWithMixedNames#0) +Test Passed (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testTestWithArrayManyCasesWithMixedNames#0) +Test Finished (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testTestWithArrayManyCasesWithMixedNames#0) +Test Preparation Started (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testTestWithArrayManyCasesWithMixedNames#evens) +Test Prepared (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testTestWithArrayManyCasesWithMixedNames#evens) +Test Passed (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testTestWithArrayManyCasesWithMixedNames#evens) +Test Finished (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testTestWithArrayManyCasesWithMixedNames#evens) +Test Suite Finished (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testTestWithArrayManyCasesWithMixedNames, 3 tests) +Test Suite Started (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testMultipleTestWithArray, 2 tests) +Test Preparation Started (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testMultipleTestWithArray#0) +Test Prepared (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testMultipleTestWithArray#0) +Test Passed (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testMultipleTestWithArray#0) +Test Finished (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testMultipleTestWithArray#0) +Test Preparation Started (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testMultipleTestWithArray#1) +Test Prepared (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testMultipleTestWithArray#1) +Test Passed (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testMultipleTestWithArray#1) +Test Finished (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testMultipleTestWithArray#1) +Test Suite Finished (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testMultipleTestWithArray, 2 tests) +Test Suite Finished (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest, 11 tests) Test Runner Execution Finished Test Runner Finished PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/unit/Metadata/Parser/AttributeParserTest.php b/tests/unit/Metadata/Parser/AttributeParserTest.php index 81bb3a45cd..a96288f72c 100644 --- a/tests/unit/Metadata/Parser/AttributeParserTest.php +++ b/tests/unit/Metadata/Parser/AttributeParserTest.php @@ -60,6 +60,7 @@ use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\Attributes\TestDox; use PHPUnit\Framework\Attributes\TestWith; +use PHPUnit\Framework\Attributes\TestWithArray; use PHPUnit\Framework\Attributes\TestWithJson; use PHPUnit\Framework\Attributes\Ticket; use PHPUnit\Framework\Attributes\UsesClass; @@ -127,6 +128,7 @@ #[CoversClass(Small::class)] #[CoversClass(TestDox::class)] #[CoversClass(Test::class)] +#[CoversClass(TestWithArray::class)] #[CoversClass(TestWithJson::class)] #[CoversClass(TestWith::class)] #[CoversClass(Ticket::class)] diff --git a/tests/unit/Metadata/Parser/AttributeParserTestCase.php b/tests/unit/Metadata/Parser/AttributeParserTestCase.php index 780578d61f..ad612d2bd2 100644 --- a/tests/unit/Metadata/Parser/AttributeParserTestCase.php +++ b/tests/unit/Metadata/Parser/AttributeParserTestCase.php @@ -21,6 +21,7 @@ use PHPUnit\Metadata\RequiresPhpunit; use PHPUnit\Metadata\RequiresPhpunitExtension; use PHPUnit\Metadata\RequiresSetting; +use PHPUnit\Metadata\TestWith; use PHPUnit\Metadata\Version\ComparisonRequirement; use PHPUnit\Metadata\Version\ConstraintRequirement; use PHPUnit\Metadata\WithEnvironmentVariable; @@ -1094,10 +1095,11 @@ public function test_parses_TestWith_attribute_on_method(): void $metadata = $this->parser()->forMethod(TestWithTest::class, 'testOne')->isTestWith(); $this->assertCount(1, $metadata); - $this->assertTrue($metadata->asArray()[0]->isTestWith()); - $this->assertSame([1, 2, 3], $metadata->asArray()[0]->data()); - $this->assertFalse($metadata->asArray()[0]->hasName()); - $this->assertNull($metadata->asArray()[0]->name()); + $firstCase = $metadata->asArray()[0]; + $this->assertTrue($firstCase->isTestWith()); + $this->assertSame([1, 2, 3], $firstCase->data()); + $this->assertFalse($firstCase->hasName()); + $this->assertNull($firstCase->name()); } #[TestDox('Parses #[TestWith] attribute with name on method')] @@ -1106,10 +1108,52 @@ public function test_parses_TestWith_attribute_with_name_on_method(): void $metadata = $this->parser()->forMethod(TestWithTest::class, 'testOneWithName')->isTestWith(); $this->assertCount(1, $metadata); - $this->assertTrue($metadata->asArray()[0]->isTestWith()); - $this->assertSame([1, 2, 3], $metadata->asArray()[0]->data()); - $this->assertTrue($metadata->asArray()[0]->hasName()); - $this->assertSame('Name1', $metadata->asArray()[0]->name()); + $firstCase = $metadata->asArray()[0]; + $this->assertTrue($firstCase->isTestWith()); + $this->assertSame([1, 2, 3], $firstCase->data()); + $this->assertTrue($firstCase->hasName()); + $this->assertSame('Name1', $firstCase->name()); + } + + #[TestDox('Parses #[TestWithArray] attribute(s) on method')] + public function test_parses_TestWithArray_attribute_on_method(): void + { + /** @see TestWithTest::testTestWithArrayBasic() */ + $metadata = $this->parser()->forMethod(TestWithTest::class, 'testTestWithArrayBasic'); + $this->assertCount(1, $metadata); + + /** @var TestWith $firstCase */ + $firstCase = $metadata->asArray()[0]; + $this->assertTrue($firstCase->isTestWith()); + $this->assertSame([1, true], $firstCase->data()); + $this->assertFalse($firstCase->hasName()); + $this->assertNull($firstCase->name()); + + /** @see TestWithTest::testTestWithArrayNamedCase() */ + $metadata = $this->parser()->forMethod(TestWithTest::class, 'testTestWithArrayNamedCase')->isTestWith(); + + /** @var TestWith $firstCase */ + $firstCase = $metadata->asArray()[0]; + $this->assertTrue($firstCase->hasName()); + $this->assertSame('firstCase', $firstCase->name()); + + /** @see TestWithTest::testTestWithArrayManyCasesWithMixedNames() */ + $metadata = $this->parser()->forMethod(TestWithTest::class, 'testTestWithArrayManyCasesWithMixedNames')->isTestWith(); + $this->assertCount(3, $metadata); + + /** @var TestWith[] $cases */ + $cases = $metadata->asArray(); + $this->assertSame('odds', $cases[0]->name()); + $this->assertNull($cases[1]->name()); + $this->assertSame('evens', $cases[2]->name()); + + /** @see TestWithTest::testMultipleTestWithArray() */ + $metadata = $this->parser()->forMethod(TestWithTest::class, 'testMultipleTestWithArray')->isTestWith(); + + /** @var TestWith[] $cases */ + $cases = $metadata->asArray(); + $this->assertSame([5], $cases[0]->data()); + $this->assertSame([6], $cases[1]->data()); } #[TestDox('Parses #[TestWithJson] attribute on method')] @@ -1118,10 +1162,11 @@ public function test_parses_TestWithJson_attribute_on_method(): void $metadata = $this->parser()->forMethod(TestWithTest::class, 'testTwo')->isTestWith(); $this->assertCount(1, $metadata); - $this->assertTrue($metadata->asArray()[0]->isTestWith()); - $this->assertSame([1, 2, 3], $metadata->asArray()[0]->data()); - $this->assertFalse($metadata->asArray()[0]->hasName()); - $this->assertNull($metadata->asArray()[0]->name()); + $firstCase = $metadata->asArray()[0]; + $this->assertTrue($firstCase->isTestWith()); + $this->assertSame([1, 2, 3], $firstCase->data()); + $this->assertFalse($firstCase->hasName()); + $this->assertNull($firstCase->name()); } #[TestDox('Parses #[TestWithJson] attribute with name on method')] @@ -1130,10 +1175,11 @@ public function test_parses_TestWithJson_attribute_with_name_on_method(): void $metadata = $this->parser()->forMethod(TestWithTest::class, 'testTwoWithName')->isTestWith(); $this->assertCount(1, $metadata); - $this->assertTrue($metadata->asArray()[0]->isTestWith()); - $this->assertSame([1, 2, 3], $metadata->asArray()[0]->data()); - $this->assertTrue($metadata->asArray()[0]->hasName()); - $this->assertSame('Name2', $metadata->asArray()[0]->name()); + $firstCase = $metadata->asArray()[0]; + $this->assertTrue($firstCase->isTestWith()); + $this->assertSame([1, 2, 3], $firstCase->data()); + $this->assertTrue($firstCase->hasName()); + $this->assertSame('Name2', $firstCase->name()); } #[TestDox('Parses #[Ticket] attribute on method')]