Skip to content

Instantly share code, notes, and snippets.

@nuernbergerA
Created March 27, 2020 16:48
Show Gist options
  • Save nuernbergerA/f2a45317ea32d714ea1413625ba0d1ff to your computer and use it in GitHub Desktop.
Save nuernbergerA/f2a45317ea32d714ea1413625ba0d1ff to your computer and use it in GitHub Desktop.
expectsQuestionWithAutocomplete
<?php
$this->artisan('make:factory-reloaded')
->expectsQuestionWithAutocomplete('Please pick a model',
'<href=file://'.__DIR__.'/Models/Group.php>Christophrumpel\LaravelFactoriesReloaded\Tests\Models\Group</>',
[
'All',
'<href=file://'.__DIR__.'/Models/Recipe.php>Christophrumpel\LaravelFactoriesReloaded\Tests\Models\Recipe</>',
'<href=file://'.__DIR__.'/Models/Group.php>Christophrumpel\LaravelFactoriesReloaded\Tests\Models\Group</>',
'<href=file://'.__DIR__.'/Models/Ingredient.php>Christophrumpel\LaravelFactoriesReloaded\Tests\Models\Ingredient</>',
]
)
->assertExitCode(0);
<?php
namespace Illuminate\Foundation\Testing\Concerns;
use Illuminate\Console\OutputStyle;
use Illuminate\Contracts\Console\Kernel;
use Illuminate\Support\Arr;
use Illuminate\Testing\PendingCommand;
trait InteractsWithConsole
{
/**
* Indicates if the console output should be mocked.
*
* @var bool
*/
public $mockConsoleOutput = true;
/**
* All of the expected output lines.
*
* @var array
*/
public $expectedOutput = [];
/**
* All of the expected questions.
*
* @var array
*/
public $expectedQuestions = [];
/**
* All of the expected Answers.
*
* @var array
*/
public $expectedAutocomplete = [];
/**
* Call artisan command and return code.
*
* @param string $command
* @param array $parameters
* @return \Illuminate\Testing\PendingCommand|int
*/
public function artisan($command, $parameters = [])
{
if (! $this->mockConsoleOutput) {
return $this->app[Kernel::class]->call($command, $parameters);
}
$this->beforeApplicationDestroyed(function () {
if (count($this->expectedQuestions)) {
$this->fail('Question "'.Arr::first($this->expectedQuestions)[0].'" was not asked.');
}
if (count($this->expectedAutocomplete)) {
foreach($this->expectedAutocomplete as $question => $values)
{
$this->assertEqualsCanonicalizing($values['expected'], $values['actual'], 'Question "'.$question.'" has different options.');
}
}
if (count($this->expectedOutput)) {
$this->fail('Output "'.Arr::first($this->expectedOutput).'" was not printed.');
}
});
return new PendingCommand($this, $this->app, $command, $parameters);
}
/**
* Disable mocking the console output.
*
* @return $this
*/
protected function withoutMockingConsoleOutput()
{
$this->mockConsoleOutput = false;
$this->app->offsetUnset(OutputStyle::class);
return $this;
}
}
<?php
namespace Illuminate\Testing;
use Illuminate\Console\OutputStyle;
use Illuminate\Contracts\Console\Kernel;
use Illuminate\Contracts\Container\Container;
use Mockery;
use Mockery\Exception\NoMatchingExpectationException;
use PHPUnit\Framework\TestCase as PHPUnitTestCase;
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;
/**
* Determine if 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
* @param string[] $answers
* @return $this
*/
public function expectsQuestionWithAutocomplete($question, $answer, $answers)
{
$this->test->expectedAutocomplete[$question]['expected'] = $answers;
return $this->expectsQuestion($question, $answer);
}
/**
* 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 output that should be printed when the command runs.
*
* @param string $output
* @return $this
*/
public function expectsOutput($output)
{
$this->test->expectedOutput[] = $output;
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;
}
/**
* Execute the command.
*
* @return int
*/
public function execute()
{
return $this->run();
}
/**
* Execute the command.
*
* @return int
*/
public function run()
{
$this->hasExecuted = true;
$this->mockConsoleOutput();
try {
$exitCode = $this->app->make(Kernel::class)->call($this->command, $this->parameters);
} 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}."
);
}
return $exitCode;
}
/**
* Mock the application's console output.
*
* @return void
*/
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->expectedAutocomplete[$question[0]])) {
$this->test->expectedAutocomplete[$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;
});
}
/**
* 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]);
});
}
return $mock;
}
/**
* Handle the object's destruction.
*
* @return void
*/
public function __destruct()
{
if ($this->hasExecuted) {
return;
}
$this->run();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment