Blame | Letzte Änderung | Log anzeigen | RSS feed
<?phpnamespace Illuminate\Testing;use Illuminate\Console\OutputStyle;use Illuminate\Contracts\Console\Kernel;use Illuminate\Contracts\Container\Container;use Illuminate\Contracts\Support\Arrayable;use Illuminate\Support\Arr;use Mockery;use Mockery\Exception\NoMatchingExpectationException;use PHPUnit\Framework\TestCase as PHPUnitTestCase;use Symfony\Component\Console\Command\Command;use Symfony\Component\Console\Helper\Table;use Symfony\Component\Console\Input\ArrayInput;use Symfony\Component\Console\Output\BufferedOutput;class PendingCommand{/*** The test being run.** @var \Illuminate\Foundation\Testing\TestCase*/public $test;/*** The application instance.** @var \Illuminate\Contracts\Container\Container*/protected $app;/*** The command to run.** @var string*/protected $command;/*** The parameters to pass to the command.** @var array*/protected $parameters;/*** The expected exit code.** @var int*/protected $expectedExitCode;/*** The unexpected exit code.** @var int*/protected $unexpectedExitCode;/*** Determine if the command has executed.** @var bool*/protected $hasExecuted = false;/*** Create a new pending console command run.** @param \PHPUnit\Framework\TestCase $test* @param \Illuminate\Contracts\Container\Container $app* @param string $command* @param array $parameters* @return void*/public function __construct(PHPUnitTestCase $test, Container $app, $command, $parameters){$this->app = $app;$this->test = $test;$this->command = $command;$this->parameters = $parameters;}/*** Specify an expected question that will be asked when the command runs.** @param string $question* @param string|bool $answer* @return $this*/public function expectsQuestion($question, $answer){$this->test->expectedQuestions[] = [$question, $answer];return $this;}/*** Specify an expected confirmation question that will be asked when the command runs.** @param string $question* @param string $answer* @return $this*/public function expectsConfirmation($question, $answer = 'no'){return $this->expectsQuestion($question, strtolower($answer) === 'yes');}/*** Specify an expected choice question with expected answers that will be asked/shown when the command runs.** @param string $question* @param string|array $answer* @param array $answers* @param bool $strict* @return $this*/public function expectsChoice($question, $answer, $answers, $strict = false){$this->test->expectedChoices[$question] = ['expected' => $answers,'strict' => $strict,];return $this->expectsQuestion($question, $answer);}/*** Specify output that should be printed when the command runs.** @param string $output* @return $this*/public function expectsOutput($output){$this->test->expectedOutput[] = $output;return $this;}/*** Specify output that should never be printed when the command runs.** @param string $output* @return $this*/public function doesntExpectOutput($output){$this->test->unexpectedOutput[$output] = false;return $this;}/*** Specify that the given string should be contained in the command output.** @param string $string* @return $this*/public function expectsOutputToContain($string){$this->test->expectedOutputSubstrings[] = $string;return $this;}/*** Specify that the given string shouldn't be contained in the command output.** @param string $string* @return $this*/public function doesntExpectOutputToContain($string){$this->test->unexpectedOutputSubstrings[$string] = false;return $this;}/*** Specify a table that should be printed when the command runs.** @param array $headers* @param \Illuminate\Contracts\Support\Arrayable|array $rows* @param string $tableStyle* @param array $columnStyles* @return $this*/public function expectsTable($headers, $rows, $tableStyle = 'default', array $columnStyles = []){$table = (new Table($output = new BufferedOutput))->setHeaders((array) $headers)->setRows($rows instanceof Arrayable ? $rows->toArray() : $rows)->setStyle($tableStyle);foreach ($columnStyles as $columnIndex => $columnStyle) {$table->setColumnStyle($columnIndex, $columnStyle);}$table->render();$lines = array_filter(explode(PHP_EOL, $output->fetch()));foreach ($lines as $line) {$this->expectsOutput($line);}return $this;}/*** Assert that the command has the given exit code.** @param int $exitCode* @return $this*/public function assertExitCode($exitCode){$this->expectedExitCode = $exitCode;return $this;}/*** Assert that the command does not have the given exit code.** @param int $exitCode* @return $this*/public function assertNotExitCode($exitCode){$this->unexpectedExitCode = $exitCode;return $this;}/*** Assert that the command has the success exit code.** @return $this*/public function assertSuccessful(){return $this->assertExitCode(Command::SUCCESS);}/*** Assert that the command has the success exit code.** @return $this*/public function assertOk(){return $this->assertSuccessful();}/*** Assert that the command does not have the success exit code.** @return $this*/public function assertFailed(){return $this->assertNotExitCode(Command::SUCCESS);}/*** Execute the command.** @return int*/public function execute(){return $this->run();}/*** Execute the command.** @return int** @throws \Mockery\Exception\NoMatchingExpectationException*/public function run(){$this->hasExecuted = true;$mock = $this->mockConsoleOutput();try {$exitCode = $this->app->make(Kernel::class)->call($this->command, $this->parameters, $mock);} catch (NoMatchingExpectationException $e) {if ($e->getMethodName() === 'askQuestion') {$this->test->fail('Unexpected question "'.$e->getActualArguments()[0]->getQuestion().'" was asked.');}throw $e;}if ($this->expectedExitCode !== null) {$this->test->assertEquals($this->expectedExitCode, $exitCode,"Expected status code {$this->expectedExitCode} but received {$exitCode}.");} elseif (! is_null($this->unexpectedExitCode)) {$this->test->assertNotEquals($this->unexpectedExitCode, $exitCode,"Unexpected status code {$this->unexpectedExitCode} was received.");}$this->verifyExpectations();$this->flushExpectations();return $exitCode;}/*** Determine if expected questions / choices / outputs are fulfilled.** @return void*/protected function verifyExpectations(){if (count($this->test->expectedQuestions)) {$this->test->fail('Question "'.Arr::first($this->test->expectedQuestions)[0].'" was not asked.');}if (count($this->test->expectedChoices) > 0) {foreach ($this->test->expectedChoices as $question => $answers) {$assertion = $answers['strict'] ? 'assertEquals' : 'assertEqualsCanonicalizing';$this->test->{$assertion}($answers['expected'],$answers['actual'],'Question "'.$question.'" has different options.');}}if (count($this->test->expectedOutput)) {$this->test->fail('Output "'.Arr::first($this->test->expectedOutput).'" was not printed.');}if (count($this->test->expectedOutputSubstrings)) {$this->test->fail('Output does not contain "'.Arr::first($this->test->expectedOutputSubstrings).'".');}if ($output = array_search(true, $this->test->unexpectedOutput)) {$this->test->fail('Output "'.$output.'" was printed.');}if ($output = array_search(true, $this->test->unexpectedOutputSubstrings)) {$this->test->fail('Output "'.$output.'" was printed.');}}/*** Mock the application's console output.** @return \Mockery\MockInterface*/protected function mockConsoleOutput(){$mock = Mockery::mock(OutputStyle::class.'[askQuestion]', [new ArrayInput($this->parameters), $this->createABufferedOutputMock(),]);foreach ($this->test->expectedQuestions as $i => $question) {$mock->shouldReceive('askQuestion')->once()->ordered()->with(Mockery::on(function ($argument) use ($question) {if (isset($this->test->expectedChoices[$question[0]])) {$this->test->expectedChoices[$question[0]]['actual'] = $argument->getAutocompleterValues();}return $argument->getQuestion() == $question[0];}))->andReturnUsing(function () use ($question, $i) {unset($this->test->expectedQuestions[$i]);return $question[1];});}$this->app->bind(OutputStyle::class, function () use ($mock) {return $mock;});return $mock;}/*** Create a mock for the buffered output.** @return \Mockery\MockInterface*/private function createABufferedOutputMock(){$mock = Mockery::mock(BufferedOutput::class.'[doWrite]')->shouldAllowMockingProtectedMethods()->shouldIgnoreMissing();foreach ($this->test->expectedOutput as $i => $output) {$mock->shouldReceive('doWrite')->once()->ordered()->with($output, Mockery::any())->andReturnUsing(function () use ($i) {unset($this->test->expectedOutput[$i]);});}foreach ($this->test->expectedOutputSubstrings as $i => $text) {$mock->shouldReceive('doWrite')->atLeast()->times(0)->withArgs(fn ($output) => str_contains($output, $text))->andReturnUsing(function () use ($i) {unset($this->test->expectedOutputSubstrings[$i]);});}foreach ($this->test->unexpectedOutput as $output => $displayed) {$mock->shouldReceive('doWrite')->atLeast()->times(0)->ordered()->with($output, Mockery::any())->andReturnUsing(function () use ($output) {$this->test->unexpectedOutput[$output] = true;});}foreach ($this->test->unexpectedOutputSubstrings as $text => $displayed) {$mock->shouldReceive('doWrite')->atLeast()->times(0)->withArgs(fn ($output) => str_contains($output, $text))->andReturnUsing(function () use ($text) {$this->test->unexpectedOutputSubstrings[$text] = true;});}return $mock;}/*** Flush the expectations from the test case.** @return void*/protected function flushExpectations(){$this->test->expectedOutput = [];$this->test->expectedOutputSubstrings = [];$this->test->unexpectedOutput = [];$this->test->unexpectedOutputSubstrings = [];$this->test->expectedTables = [];$this->test->expectedQuestions = [];$this->test->expectedChoices = [];}/*** Handle the object's destruction.** @return void*/public function __destruct(){if ($this->hasExecuted) {return;}$this->run();}}