Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
namespace Illuminate\Testing;
4
 
5
use Illuminate\Console\OutputStyle;
6
use Illuminate\Contracts\Console\Kernel;
7
use Illuminate\Contracts\Container\Container;
8
use Illuminate\Contracts\Support\Arrayable;
9
use Illuminate\Support\Arr;
10
use Mockery;
11
use Mockery\Exception\NoMatchingExpectationException;
12
use PHPUnit\Framework\TestCase as PHPUnitTestCase;
13
use Symfony\Component\Console\Command\Command;
14
use Symfony\Component\Console\Helper\Table;
15
use Symfony\Component\Console\Input\ArrayInput;
16
use Symfony\Component\Console\Output\BufferedOutput;
17
 
18
class PendingCommand
19
{
20
    /**
21
     * The test being run.
22
     *
23
     * @var \Illuminate\Foundation\Testing\TestCase
24
     */
25
    public $test;
26
 
27
    /**
28
     * The application instance.
29
     *
30
     * @var \Illuminate\Contracts\Container\Container
31
     */
32
    protected $app;
33
 
34
    /**
35
     * The command to run.
36
     *
37
     * @var string
38
     */
39
    protected $command;
40
 
41
    /**
42
     * The parameters to pass to the command.
43
     *
44
     * @var array
45
     */
46
    protected $parameters;
47
 
48
    /**
49
     * The expected exit code.
50
     *
51
     * @var int
52
     */
53
    protected $expectedExitCode;
54
 
55
    /**
56
     * The unexpected exit code.
57
     *
58
     * @var int
59
     */
60
    protected $unexpectedExitCode;
61
 
62
    /**
63
     * Determine if the command has executed.
64
     *
65
     * @var bool
66
     */
67
    protected $hasExecuted = false;
68
 
69
    /**
70
     * Create a new pending console command run.
71
     *
72
     * @param  \PHPUnit\Framework\TestCase  $test
73
     * @param  \Illuminate\Contracts\Container\Container  $app
74
     * @param  string  $command
75
     * @param  array  $parameters
76
     * @return void
77
     */
78
    public function __construct(PHPUnitTestCase $test, Container $app, $command, $parameters)
79
    {
80
        $this->app = $app;
81
        $this->test = $test;
82
        $this->command = $command;
83
        $this->parameters = $parameters;
84
    }
85
 
86
    /**
87
     * Specify an expected question that will be asked when the command runs.
88
     *
89
     * @param  string  $question
90
     * @param  string|bool  $answer
91
     * @return $this
92
     */
93
    public function expectsQuestion($question, $answer)
94
    {
95
        $this->test->expectedQuestions[] = [$question, $answer];
96
 
97
        return $this;
98
    }
99
 
100
    /**
101
     * Specify an expected confirmation question that will be asked when the command runs.
102
     *
103
     * @param  string  $question
104
     * @param  string  $answer
105
     * @return $this
106
     */
107
    public function expectsConfirmation($question, $answer = 'no')
108
    {
109
        return $this->expectsQuestion($question, strtolower($answer) === 'yes');
110
    }
111
 
112
    /**
113
     * Specify an expected choice question with expected answers that will be asked/shown when the command runs.
114
     *
115
     * @param  string  $question
116
     * @param  string|array  $answer
117
     * @param  array  $answers
118
     * @param  bool  $strict
119
     * @return $this
120
     */
121
    public function expectsChoice($question, $answer, $answers, $strict = false)
122
    {
123
        $this->test->expectedChoices[$question] = [
124
            'expected' => $answers,
125
            'strict' => $strict,
126
        ];
127
 
128
        return $this->expectsQuestion($question, $answer);
129
    }
130
 
131
    /**
132
     * Specify output that should be printed when the command runs.
133
     *
134
     * @param  string  $output
135
     * @return $this
136
     */
137
    public function expectsOutput($output)
138
    {
139
        $this->test->expectedOutput[] = $output;
140
 
141
        return $this;
142
    }
143
 
144
    /**
145
     * Specify output that should never be printed when the command runs.
146
     *
147
     * @param  string  $output
148
     * @return $this
149
     */
150
    public function doesntExpectOutput($output)
151
    {
152
        $this->test->unexpectedOutput[$output] = false;
153
 
154
        return $this;
155
    }
156
 
157
    /**
158
     * Specify that the given string should be contained in the command output.
159
     *
160
     * @param  string  $string
161
     * @return $this
162
     */
163
    public function expectsOutputToContain($string)
164
    {
165
        $this->test->expectedOutputSubstrings[] = $string;
166
 
167
        return $this;
168
    }
169
 
170
    /**
171
     * Specify that the given string shouldn't be contained in the command output.
172
     *
173
     * @param  string  $string
174
     * @return $this
175
     */
176
    public function doesntExpectOutputToContain($string)
177
    {
178
        $this->test->unexpectedOutputSubstrings[$string] = false;
179
 
180
        return $this;
181
    }
182
 
183
    /**
184
     * Specify a table that should be printed when the command runs.
185
     *
186
     * @param  array  $headers
187
     * @param  \Illuminate\Contracts\Support\Arrayable|array  $rows
188
     * @param  string  $tableStyle
189
     * @param  array  $columnStyles
190
     * @return $this
191
     */
192
    public function expectsTable($headers, $rows, $tableStyle = 'default', array $columnStyles = [])
193
    {
194
        $table = (new Table($output = new BufferedOutput))
195
            ->setHeaders((array) $headers)
196
            ->setRows($rows instanceof Arrayable ? $rows->toArray() : $rows)
197
            ->setStyle($tableStyle);
198
 
199
        foreach ($columnStyles as $columnIndex => $columnStyle) {
200
            $table->setColumnStyle($columnIndex, $columnStyle);
201
        }
202
 
203
        $table->render();
204
 
205
        $lines = array_filter(
206
            explode(PHP_EOL, $output->fetch())
207
        );
208
 
209
        foreach ($lines as $line) {
210
            $this->expectsOutput($line);
211
        }
212
 
213
        return $this;
214
    }
215
 
216
    /**
217
     * Assert that the command has the given exit code.
218
     *
219
     * @param  int  $exitCode
220
     * @return $this
221
     */
222
    public function assertExitCode($exitCode)
223
    {
224
        $this->expectedExitCode = $exitCode;
225
 
226
        return $this;
227
    }
228
 
229
    /**
230
     * Assert that the command does not have the given exit code.
231
     *
232
     * @param  int  $exitCode
233
     * @return $this
234
     */
235
    public function assertNotExitCode($exitCode)
236
    {
237
        $this->unexpectedExitCode = $exitCode;
238
 
239
        return $this;
240
    }
241
 
242
    /**
243
     * Assert that the command has the success exit code.
244
     *
245
     * @return $this
246
     */
247
    public function assertSuccessful()
248
    {
249
        return $this->assertExitCode(Command::SUCCESS);
250
    }
251
 
252
    /**
253
     * Assert that the command has the success exit code.
254
     *
255
     * @return $this
256
     */
257
    public function assertOk()
258
    {
259
        return $this->assertSuccessful();
260
    }
261
 
262
    /**
263
     * Assert that the command does not have the success exit code.
264
     *
265
     * @return $this
266
     */
267
    public function assertFailed()
268
    {
269
        return $this->assertNotExitCode(Command::SUCCESS);
270
    }
271
 
272
    /**
273
     * Execute the command.
274
     *
275
     * @return int
276
     */
277
    public function execute()
278
    {
279
        return $this->run();
280
    }
281
 
282
    /**
283
     * Execute the command.
284
     *
285
     * @return int
286
     *
287
     * @throws \Mockery\Exception\NoMatchingExpectationException
288
     */
289
    public function run()
290
    {
291
        $this->hasExecuted = true;
292
 
293
        $mock = $this->mockConsoleOutput();
294
 
295
        try {
296
            $exitCode = $this->app->make(Kernel::class)->call($this->command, $this->parameters, $mock);
297
        } catch (NoMatchingExpectationException $e) {
298
            if ($e->getMethodName() === 'askQuestion') {
299
                $this->test->fail('Unexpected question "'.$e->getActualArguments()[0]->getQuestion().'" was asked.');
300
            }
301
 
302
            throw $e;
303
        }
304
 
305
        if ($this->expectedExitCode !== null) {
306
            $this->test->assertEquals(
307
                $this->expectedExitCode, $exitCode,
308
                "Expected status code {$this->expectedExitCode} but received {$exitCode}."
309
            );
310
        } elseif (! is_null($this->unexpectedExitCode)) {
311
            $this->test->assertNotEquals(
312
                $this->unexpectedExitCode, $exitCode,
313
                "Unexpected status code {$this->unexpectedExitCode} was received."
314
            );
315
        }
316
 
317
        $this->verifyExpectations();
318
        $this->flushExpectations();
319
 
320
        return $exitCode;
321
    }
322
 
323
    /**
324
     * Determine if expected questions / choices / outputs are fulfilled.
325
     *
326
     * @return void
327
     */
328
    protected function verifyExpectations()
329
    {
330
        if (count($this->test->expectedQuestions)) {
331
            $this->test->fail('Question "'.Arr::first($this->test->expectedQuestions)[0].'" was not asked.');
332
        }
333
 
334
        if (count($this->test->expectedChoices) > 0) {
335
            foreach ($this->test->expectedChoices as $question => $answers) {
336
                $assertion = $answers['strict'] ? 'assertEquals' : 'assertEqualsCanonicalizing';
337
 
338
                $this->test->{$assertion}(
339
                    $answers['expected'],
340
                    $answers['actual'],
341
                    'Question "'.$question.'" has different options.'
342
                );
343
            }
344
        }
345
 
346
        if (count($this->test->expectedOutput)) {
347
            $this->test->fail('Output "'.Arr::first($this->test->expectedOutput).'" was not printed.');
348
        }
349
 
350
        if (count($this->test->expectedOutputSubstrings)) {
351
            $this->test->fail('Output does not contain "'.Arr::first($this->test->expectedOutputSubstrings).'".');
352
        }
353
 
354
        if ($output = array_search(true, $this->test->unexpectedOutput)) {
355
            $this->test->fail('Output "'.$output.'" was printed.');
356
        }
357
 
358
        if ($output = array_search(true, $this->test->unexpectedOutputSubstrings)) {
359
            $this->test->fail('Output "'.$output.'" was printed.');
360
        }
361
    }
362
 
363
    /**
364
     * Mock the application's console output.
365
     *
366
     * @return \Mockery\MockInterface
367
     */
368
    protected function mockConsoleOutput()
369
    {
370
        $mock = Mockery::mock(OutputStyle::class.'[askQuestion]', [
371
            new ArrayInput($this->parameters), $this->createABufferedOutputMock(),
372
        ]);
373
 
374
        foreach ($this->test->expectedQuestions as $i => $question) {
375
            $mock->shouldReceive('askQuestion')
376
                ->once()
377
                ->ordered()
378
                ->with(Mockery::on(function ($argument) use ($question) {
379
                    if (isset($this->test->expectedChoices[$question[0]])) {
380
                        $this->test->expectedChoices[$question[0]]['actual'] = $argument->getAutocompleterValues();
381
                    }
382
 
383
                    return $argument->getQuestion() == $question[0];
384
                }))
385
                ->andReturnUsing(function () use ($question, $i) {
386
                    unset($this->test->expectedQuestions[$i]);
387
 
388
                    return $question[1];
389
                });
390
        }
391
 
392
        $this->app->bind(OutputStyle::class, function () use ($mock) {
393
            return $mock;
394
        });
395
 
396
        return $mock;
397
    }
398
 
399
    /**
400
     * Create a mock for the buffered output.
401
     *
402
     * @return \Mockery\MockInterface
403
     */
404
    private function createABufferedOutputMock()
405
    {
406
        $mock = Mockery::mock(BufferedOutput::class.'[doWrite]')
407
                ->shouldAllowMockingProtectedMethods()
408
                ->shouldIgnoreMissing();
409
 
410
        foreach ($this->test->expectedOutput as $i => $output) {
411
            $mock->shouldReceive('doWrite')
412
                ->once()
413
                ->ordered()
414
                ->with($output, Mockery::any())
415
                ->andReturnUsing(function () use ($i) {
416
                    unset($this->test->expectedOutput[$i]);
417
                });
418
        }
419
 
420
        foreach ($this->test->expectedOutputSubstrings as $i => $text) {
421
            $mock->shouldReceive('doWrite')
422
                ->atLeast()
423
                ->times(0)
424
                ->withArgs(fn ($output) => str_contains($output, $text))
425
                ->andReturnUsing(function () use ($i) {
426
                    unset($this->test->expectedOutputSubstrings[$i]);
427
                });
428
        }
429
 
430
        foreach ($this->test->unexpectedOutput as $output => $displayed) {
431
            $mock->shouldReceive('doWrite')
432
                ->atLeast()
433
                ->times(0)
434
                ->ordered()
435
                ->with($output, Mockery::any())
436
                ->andReturnUsing(function () use ($output) {
437
                    $this->test->unexpectedOutput[$output] = true;
438
                });
439
        }
440
 
441
        foreach ($this->test->unexpectedOutputSubstrings as $text => $displayed) {
442
            $mock->shouldReceive('doWrite')
443
                 ->atLeast()
444
                 ->times(0)
445
                 ->withArgs(fn ($output) => str_contains($output, $text))
446
                 ->andReturnUsing(function () use ($text) {
447
                     $this->test->unexpectedOutputSubstrings[$text] = true;
448
                 });
449
        }
450
 
451
        return $mock;
452
    }
453
 
454
    /**
455
     * Flush the expectations from the test case.
456
     *
457
     * @return void
458
     */
459
    protected function flushExpectations()
460
    {
461
        $this->test->expectedOutput = [];
462
        $this->test->expectedOutputSubstrings = [];
463
        $this->test->unexpectedOutput = [];
464
        $this->test->unexpectedOutputSubstrings = [];
465
        $this->test->expectedTables = [];
466
        $this->test->expectedQuestions = [];
467
        $this->test->expectedChoices = [];
468
    }
469
 
470
    /**
471
     * Handle the object's destruction.
472
     *
473
     * @return void
474
     */
475
    public function __destruct()
476
    {
477
        if ($this->hasExecuted) {
478
            return;
479
        }
480
 
481
        $this->run();
482
    }
483
}