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