Laravel Zero - это микро-фреймворк для консольных приложений на базе Laravel Framework, позволяющий легко и быстро разрабатывать функционал работающий без веба. Но инициатором этой статьи стал кейс и несколько часов потраченного времени.
Вводная
При разработке интерактивного консольного приложения на базе Laravel Zero, я решил использовать Laravel Prompts в качестве гибкого, лёгкого и красивого взаимодействия с пользователем. И, пока разработка велась под Windows, проблем не было никаких и внезапное осознание того, что под Windows всё работает, а под Linux - нет, от части привело меня в ступор...
Тестирование
Итак, всё по порядку. Допустим, в приложении имеется консольная команда с таким кодом:
use Illuminate\Console\Command;
use function Laravel\Prompts\text;
class FooCommand extends Command
{
protected $signature = 'foo';
protected $description = 'Command description';
public function handle(): void
{
$first = text('First question');
$second = text('Second question');
$this->info('First is ' . $first);
$this->info('Second is ' . $second);
}
}
Также имеем Pest тест:
use App\Commands\FooCommand;
it('some', fn() => test()->artisan(FooCommand::class)
->expectsQuestion('First question', 'qwerty')
->expectsQuestion('Second question', 'wasd')
->expectsOutputToContain('First is qwerty')
->expectsOutputToContain('Second is wasd')
->assertSuccessful()
);
При запуске теста под Windows проблем с ним никаких не будет:
$ vendor/bin/pest --filter FooTest
PASS Tests\Feature\FooTest
✓ it some 0.34s
Tests: 1 passed (10 assertions)
Duration: 0.51s
НО стоит запустить его в WSL (Ubuntu), линуксе или любом месте, что не является Windows, то сразу получаем ошибку:
$ vendor/bin/pest --filter FooTest
FAILED Tests\Feature\FooTest > it some AssertionFailedError
Question "First question" was not asked.
at vendor/illuminate/testing/PendingCommand.php:370
366▕ */
367▕ protected function verifyExpectations()
368▕ {
369▕ if (count($this->test->expectedQuestions)) {
➜ 370▕ $this->test->fail('Question "'.Arr::first($this->test->expectedQuestions)[0].'" was not asked.');
371▕ }
372▕
373▕ if (count($this->test->expectedChoices) > 0) {
374▕ foreach ($this->test->expectedChoices as $question => $answers) {
+3 vendor frames
4 tests/Feature/FooTest.php:13
Это значит, что консоль либо не может, либо даже не пытается ввести данные.
Исследование
На странице документации Prompts есть раздел где сказано, что из-за особенностей Windows, а также некоторых окружений, нужно явно использовать fallback значения.
Начиная с Laravel Framework 10.17, библиотека Laravel Prompts поставляется "в коробке", а значит явно должна этой "коробкой" использоваться. И мы не ошиблись! В коде консольных команд присутствует трейт ConfiguresPrompts с реализацией нужных fallback значений. Это нам на руку, так как не придётся изобретать велосипед.
Но вопрос в ошибке тестирования остаётся открытым. Изучая код, на четвёртой же его строчке видим функцию активации этих fallback значений:
Prompt::fallbackWhen(windows_os() || $this->laravel->runningUnitTests());
И становится понятно почему под Windows всё работает, а в остальных случаях нет. Вызов dd($this->laravel->runningUnitTests())
возвращающий false
подтверждает догадки.
А ларчик просто открывался...
Метод runningUnitTests
достаточно прост и он проверяет имя окружения под которым запущена команда:
public function runningUnitTests()
{
return $this->bound('env') && $this['env'] === 'testing';
}
Добавив над оператором возврата строчку dd($this['env'])
внезапно получаем development
. И он выглядит очень знакомым... 😑
Где в Laravel приложениях задаётся имя окружения при тестировании? Правильно! в phpunit.xml
файле. Открываем файл и что видим? А ничего мы не видим! Окружение-то не задаётся вовсе так как оно нам не нужно. Куда нужно идти в этом случае? Верно! В config/app.php
и видим...
return [
// ...
'env' => 'development',
// ...
];
🤦♂️🤦♂️🤦♂️
Решение
Теперь настройки окружения нам нужны, но не полностью. Таким образом, в файл phpunit.xml
добавляем строчки:
<php>
<env name="APP_ENV" value="testing" />
</php>
Затем в знакомом файле config/app.php
меняем значение на:
-'env' => 'development',
+'env' => env('APP_ENV', 'production'),
Запускаем тесты и наслаждаемся результатом:
$ vendor/bin/pest --parallel
.................................
Tests: 33 passed (108 assertions)
Duration: 2.69s
Parallel: 16 processes
Всё.