Pull to refresh

Ошибка тестирования с Laravel Prompts в Laravel-Zero

Level of difficultyEasy
Reading time3 min
Views904

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

Всё.

Tags:
Hubs:
If this publication inspired you and you want to support the author, do not hesitate to click on the button
Total votes 3: ↑2 and ↓1+3
Comments1

Articles