Skip to content

Commit

Permalink
Fix doesntExpectOutput() always causing a failed test (#36806)
Browse files Browse the repository at this point in the history
On what should be a passing test when the doesntExpectOutput()
arg hasn't been output, PHPUnit's tearDown() call to
Mockery::close() will throw any exception.

Mockery\Exception\InvalidCountException: Method doWrite($output, <Any>)
from Mockery_0_Symfony_Component_Console_Output_BufferedOutput should be
called exactly 1 times but called 0 times.

During a successful test run, it _should_ be called 0 times.
Remove once() to allow the `andReturnUsing()` callback to be
invoked one or many times.

And add integration test coverage to PendingCommand.
  • Loading branch information
derekmd authored Mar 30, 2021
1 parent 36c1f18 commit 7c4b97f
Show file tree
Hide file tree
Showing 2 changed files with 132 additions and 1 deletion.
1 change: 0 additions & 1 deletion src/Illuminate/Testing/PendingCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,6 @@ private function createABufferedOutputMock()

foreach ($this->test->unexpectedOutput as $output => $displayed) {
$mock->shouldReceive('doWrite')
->once()
->ordered()
->with($output, Mockery::any())
->andReturnUsing(function () use ($output) {
Expand Down
132 changes: 132 additions & 0 deletions tests/Integration/Testing/ArtisanCommandTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
<?php

namespace Illuminate\Tests\Integration\Testing;

use Illuminate\Support\Facades\Artisan;
use Mockery;
use Mockery\Exception\InvalidCountException;
use Mockery\Exception\InvalidOrderException;
use Orchestra\Testbench\TestCase;
use PHPUnit\Framework\AssertionFailedError;

class ArtisanCommandTest extends TestCase
{
protected function setUp(): void
{
parent::setUp();

Artisan::command('survey', function () {
$name = $this->ask('What is your name?');

$language = $this->choice('Which language do you prefer?', [
'PHP',
'Ruby',
'Python',
]);

$this->line("Your name is $name and you prefer $language.");
});

Artisan::command('slim', function () {
$this->line($this->ask('Who?'));
$this->line($this->ask('What?'));
$this->line($this->ask('Huh?'));
});
}

public function test_console_command_that_passes()
{
$this->artisan('survey')
->expectsQuestion('What is your name?', 'Taylor Otwell')
->expectsQuestion('Which language do you prefer?', 'PHP')
->expectsOutput('Your name is Taylor Otwell and you prefer PHP.')
->doesntExpectOutput('Your name is Taylor Otwell and you prefer Ruby.')
->assertExitCode(0);
}

public function test_console_command_that_passes_with_repeating_output()
{
$this->artisan('slim')
->expectsQuestion('Who?', 'Taylor')
->expectsQuestion('What?', 'Taylor')
->expectsQuestion('Huh?', 'Taylor')
->expectsOutput('Taylor')
->doesntExpectOutput('Otwell')
->expectsOutput('Taylor')
->expectsOutput('Taylor')
->assertExitCode(0);
}

public function test_console_command_that_fails_from_unexpected_output()
{
$this->expectException(AssertionFailedError::class);
$this->expectExceptionMessage('Output "Your name is Taylor Otwell and you prefer PHP." was printed.');

$this->artisan('survey')
->expectsQuestion('What is your name?', 'Taylor Otwell')
->expectsQuestion('Which language do you prefer?', 'PHP')
->doesntExpectOutput('Your name is Taylor Otwell and you prefer PHP.')
->assertExitCode(0);
}

public function test_console_command_that_fails_from_missing_output()
{
$this->expectException(AssertionFailedError::class);
$this->expectExceptionMessage('Output "Your name is Taylor Otwell and you prefer PHP." was not printed.');

$this->ignoringMockOnceExceptions(function () {
$this->artisan('survey')
->expectsQuestion('What is your name?', 'Taylor Otwell')
->expectsQuestion('Which language do you prefer?', 'Ruby')
->expectsOutput('Your name is Taylor Otwell and you prefer PHP.')
->assertExitCode(0);
});
}

public function test_console_command_that_fails_from_exit_code_mismatch()
{
$this->expectException(AssertionFailedError::class);
$this->expectExceptionMessage('Expected status code 1 but received 0.');

$this->artisan('survey')
->expectsQuestion('What is your name?', 'Taylor Otwell')
->expectsQuestion('Which language do you prefer?', 'PHP')
->assertExitCode(1);
}

public function test_console_command_that_fails_from_unordered_output()
{
$this->expectException(InvalidOrderException::class);

$this->ignoringMockOnceExceptions(function () {
$this->artisan('slim')
->expectsQuestion('Who?', 'Taylor')
->expectsQuestion('What?', 'Danger')
->expectsQuestion('Huh?', 'Otwell')
->expectsOutput('Taylor')
->expectsOutput('Otwell')
->expectsOutput('Danger')
->assertExitCode(0);
});
}

/**
* Don't allow Mockery's InvalidCountException to be reported. Mocks setup
* in PendingCommand cause PHPUnit tearDown() to later throw the exception.
*
* @param callable $callback
* @return void
*/
protected function ignoringMockOnceExceptions(callable $callback)
{
try {
$callback();
} finally {
try {
Mockery::close();
} catch (InvalidCountException $e) {
// Ignore mock exception from PendingCommand::expectsOutput().
}
}
}
}

0 comments on commit 7c4b97f

Please sign in to comment.