packages/requirements/tests/Unit/Test/PhpUnitRunnerTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Test;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\DataProvider;
9use PHPUnit\Framework\Attributes\Medium;
10use PHPUnit\Framework\Attributes\UsesClass;
11use PHPUnit\Framework\TestCase;
12use Requirements\Test\JUnit;
13use Requirements\Test\PhpUnitRunner;
14use Requirements\Test\ProcessRunner;
15use Requirements\Test\RunnerConfig;
16use Requirements\Test\TestResult;
17use Tests\Fake\PhpUnitSuite;
18use Tests\Fake\ProjectDirectory;
19
20#[CoversClass(PhpUnitRunner::class)]
21#[UsesClass(ProcessRunner::class)]
22#[UsesClass(RunnerConfig::class)]
23#[UsesClass(JUnit::class)]
24#[UsesClass(TestResult::class)]
25#[Medium]
26final class PhpUnitRunnerTest extends TestCase
27{
28 #[DataProvider('providerRunRunsOnlyExactMethodAndRequiresRealPassingTests')]
29 public function testRunRunsOnlyExactMethodAndRequiresRealPassingTests(string $method, string $status, int $tests): void
30 {
31 $project = new ProjectDirectory();
32 $command = PhpUnitSuite::write($project->directory);
33 self::assertNotEmpty($command);
34 $config = new RunnerConfig('phpunit', $command, $project->directory);
35 $result = (new PhpUnitRunner())->run($config, 'Sample\\PassingTest::' . $method);
36 self::assertSame($status, $result->status, $result->message);
37 self::assertSame($tests, $result->tests, $result->message);
38 }
39
40 /**
41 * @return list<array{string, string, int}>
42 */
43 public static function providerRunRunsOnlyExactMethodAndRequiresRealPassingTests(): array
44 {
45 return [['testPass', 'passed', 1], ['testFailure', 'failed', 1], ['testSkip', 'failed', 1], ['testMissing', 'error', 0], ['testData', 'passed', 2]];
46 }
47
48 public function testRunReportsTheFailureOutput(): void
49 {
50 $project = new ProjectDirectory();
51 $command = PhpUnitSuite::write($project->directory);
52 self::assertNotEmpty($command);
53 $config = new RunnerConfig('phpunit', $command, $project->directory);
54 $result = (new PhpUnitRunner())->run($config, 'Sample\\PassingTest::testFailure');
55 self::assertStringContainsString('Expected failure.', $result->message);
56 }
57
58 public function testRunTreatsTheTargetAsLiteralText(): void
59 {
60 $project = new ProjectDirectory();
61 $command = PhpUnitSuite::write($project->directory);
62 self::assertNotEmpty($command);
63 $config = new RunnerConfig('phpunit', $command, $project->directory);
64 $result = (new PhpUnitRunner())->run($config, 'Sample\\PassingTest::testPas');
65 self::assertSame('error', $result->status, $result->message);
66 self::assertSame(0, $result->tests, $result->message);
67 }
68
69 #[DataProvider('providerRunRejectsMalformedTargets')]
70 public function testRunRejectsMalformedTargets(string $target): void
71 {
72 $project = new ProjectDirectory();
73 $config = new RunnerConfig('phpunit', [PHP_BINARY, '-r', 'file_put_contents("ran.txt", "ran");'], $project->directory);
74 self::assertSame(['status' => 'error', 'tests' => 0, 'message' => 'PHPUnit targets must be fully qualified Class::method references.'], (new PhpUnitRunner())->run($config, $target)->toArray());
75 self::assertFileDoesNotExist($project->path('ran.txt'));
76 }
77
78 /**
79 * @return array<string, array{string}>
80 */
81 public static function providerRunRejectsMalformedTargets(): array
82 {
83 return [
84 'class only' => ['Sample\\PassingTest'],
85 'method only' => ['testPass'],
86 'empty method' => ['Sample\\PassingTest::'],
87 'leading digit in class' => ['1Sample\\PassingTest::testPass'],
88 'leading digit in method' => ['Sample\\PassingTest::1test'],
89 'leading backslash' => ['\\Sample\\PassingTest::testPass'],
90 'option' => ['--filter=x::y'],
91 'regex in method' => ['Sample\\PassingTest::test.*'],
92 'space' => ['Sample\\PassingTest::test Pass'],
93 'trailing newline' => ["Sample\\PassingTest::testPass\n"],
94 'data set' => ['Sample\\PassingTest::testData with data set #0'],
95 ];
96 }
97}
98