Skip to content

Commit 7c4b97f

Browse files
authored
Fix doesntExpectOutput() always causing a failed test (#36806)
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.
1 parent 36c1f18 commit 7c4b97f

File tree

2 files changed

+132
-1
lines changed

2 files changed

+132
-1
lines changed

src/Illuminate/Testing/PendingCommand.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,6 @@ private function createABufferedOutputMock()
329329

330330
foreach ($this->test->unexpectedOutput as $output => $displayed) {
331331
$mock->shouldReceive('doWrite')
332-
->once()
333332
->ordered()
334333
->with($output, Mockery::any())
335334
->andReturnUsing(function () use ($output) {
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
<?php
2+
3+
namespace Illuminate\Tests\Integration\Testing;
4+
5+
use Illuminate\Support\Facades\Artisan;
6+
use Mockery;
7+
use Mockery\Exception\InvalidCountException;
8+
use Mockery\Exception\InvalidOrderException;
9+
use Orchestra\Testbench\TestCase;
10+
use PHPUnit\Framework\AssertionFailedError;
11+
12+
class ArtisanCommandTest extends TestCase
13+
{
14+
protected function setUp(): void
15+
{
16+
parent::setUp();
17+
18+
Artisan::command('survey', function () {
19+
$name = $this->ask('What is your name?');
20+
21+
$language = $this->choice('Which language do you prefer?', [
22+
'PHP',
23+
'Ruby',
24+
'Python',
25+
]);
26+
27+
$this->line("Your name is $name and you prefer $language.");
28+
});
29+
30+
Artisan::command('slim', function () {
31+
$this->line($this->ask('Who?'));
32+
$this->line($this->ask('What?'));
33+
$this->line($this->ask('Huh?'));
34+
});
35+
}
36+
37+
public function test_console_command_that_passes()
38+
{
39+
$this->artisan('survey')
40+
->expectsQuestion('What is your name?', 'Taylor Otwell')
41+
->expectsQuestion('Which language do you prefer?', 'PHP')
42+
->expectsOutput('Your name is Taylor Otwell and you prefer PHP.')
43+
->doesntExpectOutput('Your name is Taylor Otwell and you prefer Ruby.')
44+
->assertExitCode(0);
45+
}
46+
47+
public function test_console_command_that_passes_with_repeating_output()
48+
{
49+
$this->artisan('slim')
50+
->expectsQuestion('Who?', 'Taylor')
51+
->expectsQuestion('What?', 'Taylor')
52+
->expectsQuestion('Huh?', 'Taylor')
53+
->expectsOutput('Taylor')
54+
->doesntExpectOutput('Otwell')
55+
->expectsOutput('Taylor')
56+
->expectsOutput('Taylor')
57+
->assertExitCode(0);
58+
}
59+
60+
public function test_console_command_that_fails_from_unexpected_output()
61+
{
62+
$this->expectException(AssertionFailedError::class);
63+
$this->expectExceptionMessage('Output "Your name is Taylor Otwell and you prefer PHP." was printed.');
64+
65+
$this->artisan('survey')
66+
->expectsQuestion('What is your name?', 'Taylor Otwell')
67+
->expectsQuestion('Which language do you prefer?', 'PHP')
68+
->doesntExpectOutput('Your name is Taylor Otwell and you prefer PHP.')
69+
->assertExitCode(0);
70+
}
71+
72+
public function test_console_command_that_fails_from_missing_output()
73+
{
74+
$this->expectException(AssertionFailedError::class);
75+
$this->expectExceptionMessage('Output "Your name is Taylor Otwell and you prefer PHP." was not printed.');
76+
77+
$this->ignoringMockOnceExceptions(function () {
78+
$this->artisan('survey')
79+
->expectsQuestion('What is your name?', 'Taylor Otwell')
80+
->expectsQuestion('Which language do you prefer?', 'Ruby')
81+
->expectsOutput('Your name is Taylor Otwell and you prefer PHP.')
82+
->assertExitCode(0);
83+
});
84+
}
85+
86+
public function test_console_command_that_fails_from_exit_code_mismatch()
87+
{
88+
$this->expectException(AssertionFailedError::class);
89+
$this->expectExceptionMessage('Expected status code 1 but received 0.');
90+
91+
$this->artisan('survey')
92+
->expectsQuestion('What is your name?', 'Taylor Otwell')
93+
->expectsQuestion('Which language do you prefer?', 'PHP')
94+
->assertExitCode(1);
95+
}
96+
97+
public function test_console_command_that_fails_from_unordered_output()
98+
{
99+
$this->expectException(InvalidOrderException::class);
100+
101+
$this->ignoringMockOnceExceptions(function () {
102+
$this->artisan('slim')
103+
->expectsQuestion('Who?', 'Taylor')
104+
->expectsQuestion('What?', 'Danger')
105+
->expectsQuestion('Huh?', 'Otwell')
106+
->expectsOutput('Taylor')
107+
->expectsOutput('Otwell')
108+
->expectsOutput('Danger')
109+
->assertExitCode(0);
110+
});
111+
}
112+
113+
/**
114+
* Don't allow Mockery's InvalidCountException to be reported. Mocks setup
115+
* in PendingCommand cause PHPUnit tearDown() to later throw the exception.
116+
*
117+
* @param callable $callback
118+
* @return void
119+
*/
120+
protected function ignoringMockOnceExceptions(callable $callback)
121+
{
122+
try {
123+
$callback();
124+
} finally {
125+
try {
126+
Mockery::close();
127+
} catch (InvalidCountException $e) {
128+
// Ignore mock exception from PendingCommand::expectsOutput().
129+
}
130+
}
131+
}
132+
}

0 commit comments

Comments
 (0)