laravel / prompts

Beautiful and user-friendly forms for your command-line PHP applications.
https://laravel.com/docs/prompts
MIT License
501 stars 88 forks source link

Support for Laravel Console Tests #39

Closed hettiger closed 1 year ago

hettiger commented 1 year ago

It seems like Laravel Console Tests are not supported. E.g. the function Laravel\Prompts\search seems to be just blocking the test runner.

I've tried to upgrade churchtools/changelogger to use Laravel Prompts. Everything seems to be working fine. However, the tests do fail / never finish execution.

E.g. this test just blocks: https://github.com/hettiger/changelogger/blob/2a795d5a42075ff32ff1d10e0976a3e067488c0f/tests/Feature/Commands/NewChangelogTest.php#L12-L33

It seems like the ->expectsQuestion('Type of change', 'New feature') call fails to provide the answer to e.g. the following prompt:

https://github.com/hettiger/changelogger/blob/2a795d5a42075ff32ff1d10e0976a3e067488c0f/app/Commands/NewCommand.php#L90-L97

Please add support for Laravel Console Tests to Laravel Prompts. That would be awesome.

composer show ``` $ composer show brick/math 0.11.0 Arbitrary-precision arithmetic library doctrine/inflector 2.0.8 PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words. doctrine/instantiator 2.0.0 A small, lightweight utility to instantiate objects in PHP without invoking their constructors dragonmantank/cron-expression v3.3.2 CRON for PHP: Calculate the next or previous run date and determine if a CRON expression is due filp/whoops 2.15.3 php error handling for cool kids graham-campbell/result-type v1.1.1 An Implementation Of The Result Type hamcrest/hamcrest-php v2.0.1 This is the PHP port of Hamcrest Matchers illuminate/bus v10.17.1 The Illuminate Bus package. illuminate/cache v10.17.1 The Illuminate Cache package. illuminate/collections v10.17.1 The Illuminate Collections package. illuminate/conditionable v10.17.1 The Illuminate Conditionable package. illuminate/config v10.17.1 The Illuminate Config package. illuminate/console v10.17.1 The Illuminate Console package. illuminate/container v10.17.1 The Illuminate Container package. illuminate/contracts v10.17.1 The Illuminate Contracts package. illuminate/events v10.17.1 The Illuminate Events package. illuminate/filesystem v10.17.1 The Illuminate Filesystem package. illuminate/macroable v10.17.1 The Illuminate Macroable package. illuminate/pipeline v10.17.1 The Illuminate Pipeline package. illuminate/process v10.17.1 The Illuminate Process package. illuminate/support v10.17.1 The Illuminate Support package. illuminate/testing v10.17.1 The Illuminate Testing package. illuminate/view v10.17.1 The Illuminate View package. jolicode/jolinotif v2.5.2 Send desktop notifications on Windows, Linux, MacOS. laravel-zero/foundation v10.12.0 This is a mirror from illuminate/foundation. laravel-zero/framework v10.1.1 The Laravel Zero Framework. laravel/prompts v0.1.3 league/flysystem 3.15.1 File storage abstraction for PHP league/flysystem-local 3.15.0 Local filesystem adapter for Flysystem. league/mime-type-detection 1.12.0 Mime-type detection for Flysystem mockery/mockery 1.6.4 Mockery is a simple yet flexible PHP mock object framework myclabs/deep-copy 1.11.1 Create deep copies (clones) of your objects nesbot/carbon 2.68.1 An API extension for DateTime that supports 281 different languages. nikic/php-parser v4.16.0 A PHP parser written in PHP nunomaduro/collision v7.8.0 Cli error handling for console/command-line PHP applications. nunomaduro/laravel-console-summary v1.10.0 A Beautiful Laravel Console Summary for your Laravel/Laravel Zero commands. nunomaduro/laravel-console-task v1.8.0 Laravel Console Task is a output method for your Laravel/Laravel Zero commands. nunomaduro/laravel-desktop-notifier v2.7.0 Send notifications to your desktop from your Laravel commands. An JoliNotif wrapper for Laravel 5. nunomaduro/termwind v1.15.1 Its like Tailwind CSS, but for the console. phar-io/manifest 2.0.3 Component for reading phar.io manifest information from a PHP Archive (PHAR) phar-io/version 3.2.1 Library for handling version information and constraints phpoption/phpoption 1.9.1 Option Type for PHP phpunit/php-code-coverage 9.2.27 Library that provides collection, processing, and rendering functionality for PHP code coverage information. phpunit/php-file-iterator 3.0.6 FilterIterator implementation that filters files based on a list of suffixes. phpunit/php-invoker 3.1.1 Invoke callables with a timeout phpunit/php-text-template 2.0.4 Simple template engine. phpunit/php-timer 5.0.3 Utility class for timing phpunit/phpunit 9.6.10 The PHP Unit Testing framework. psr/container 2.0.2 Common Container Interface (PHP FIG PSR-11) psr/event-dispatcher 1.0.0 Standard interfaces for event handling. psr/log 3.0.0 Common interface for logging libraries psr/simple-cache 3.0.0 Common interfaces for simple caching ramsey/collection 2.0.0 A PHP library for representing and manipulating collections. ramsey/uuid 4.7.4 A PHP library for generating and working with universally unique identifiers (UUIDs). sebastian/cli-parser 1.0.1 Library for parsing CLI options sebastian/code-unit 1.0.8 Collection of value objects that represent the PHP code units sebastian/code-unit-reverse-lookup 2.0.3 Looks up which function or method a line of code belongs to sebastian/comparator 4.0.8 Provides the functionality to compare PHP values for equality sebastian/complexity 2.0.2 Library for calculating the complexity of PHP code units sebastian/diff 4.0.5 Diff implementation sebastian/environment 5.1.5 Provides functionality to handle HHVM/PHP environments sebastian/exporter 4.0.5 Provides the functionality to export PHP variables for visualization sebastian/global-state 5.0.6 Snapshotting of global state sebastian/lines-of-code 1.0.3 Library for counting the lines of code in PHP source code sebastian/object-enumerator 4.0.4 Traverses array structures and object graphs to enumerate all referenced objects sebastian/object-reflector 2.0.4 Allows reflection of object attributes, including inherited and non-public ones sebastian/recursion-context 4.0.5 Provides functionality to recursively process PHP variables sebastian/resource-operations 3.0.3 Provides a list of PHP built-in functions that operate on resources sebastian/type 3.2.1 Collection of value objects that represent the types of the PHP type system sebastian/version 3.0.2 Library that helps with managing the version number of Git-hosted PHP projects symfony/console v6.3.2 Eases the creation of beautiful and testable command line interfaces symfony/deprecation-contracts v3.3.0 A generic function and convention to trigger deprecation notices symfony/error-handler v6.3.2 Provides tools to manage errors and ease debugging PHP code symfony/event-dispatcher v6.3.2 Provides tools that allow your application components to communicate with each other by dispatching events and listening to them symfony/event-dispatcher-contracts v3.3.0 Generic abstractions related to dispatching event symfony/finder v6.3.3 Finds files and directories via an intuitive fluent interface symfony/polyfill-ctype v1.27.0 Symfony polyfill for ctype functions symfony/polyfill-intl-grapheme v1.27.0 Symfony polyfill for intl's grapheme_* functions symfony/polyfill-intl-normalizer v1.27.0 Symfony polyfill for intl's Normalizer class and related functions symfony/polyfill-mbstring v1.27.0 Symfony polyfill for the Mbstring extension symfony/polyfill-php80 v1.27.0 Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions symfony/process v6.3.2 Executes commands in sub-processes symfony/service-contracts v3.3.0 Generic abstractions related to writing services symfony/string v6.3.2 Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way symfony/translation v6.3.3 Provides tools to internationalize your application symfony/translation-contracts v3.3.0 Generic abstractions related to translation symfony/var-dumper v6.3.3 Provides mechanisms for walking through any arbitrary PHP variable symfony/yaml v5.4.23 Loads and dumps YAML files theseer/tokenizer 1.2.1 A small library for converting tokenized PHP source code into XML and potentially other formats vlucas/phpdotenv v5.5.0 Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically. voku/portable-ascii 2.0.1 Portable ASCII library - performance optimized (ascii) string functions for php. webmozart/assert 1.11.0 Assertions to validate method input/output with nice error messages. ```
jessarcher commented 1 year ago

Hi @hettiger,

In laravel/framework ^10.17, we configure Prompts to fall back to the Symfony implementation when running unit tests, which allows the expectQuestion test helper to work:

https://github.com/laravel/framework/blob/dd5c5178274e64d0384dc30bf2c8139b00dba098/src/Illuminate/Console/Concerns/ConfiguresPrompts.php#L27

I've just double-checked, and the expectQuestion test helper works for me in a fresh Laravel install. The problem relates to how Laravel Zero sets up your application, but there are a few ways to fix it.

The problem is that app()->runningUnitTest() returns false in Laravel Zero and your project. This is because:

I'm not sure if there is a reason that Laravel Zero does this, but a solution we can propose (and also apply to your repo) is:

With these changes in place, your tests pass.

If the above changes cause other issues, an alternative would be to directly call \Laravel\Prompts\Prompt::fallbackWhen(true) in the tests, probably somewhere central like TestCase or CreatesApplication.

sc85 commented 1 year ago

In my case I was able to get it to work by asserting twice with the same question but first expecting a question and after that expecting a choice.

->expectsQuestion('Type of change', 'New feature') // Question with input to prompt the search
->expectsChoice(
    'Type of change',
    'New feature', // Final selection
   [
       0, // Index of a possible selection
       'New feature', // Value of a selection
   ],
)

I came up with this after switching my local env to the fallback mode and noticed that this is basically the behavior of search in fallback mode.

hettiger commented 1 year ago

The problem is that app()->runningUnitTest() returns false in Laravel Zero and your project. This is because:

  • In the config/app.php file from Laravel Zero (and your repo) the env key is hardcoded to development.
  • The phpunit.xml.dist file from Laravel Zero (and your repo) doesn't set the APP_ENV environment variable to testing as Laravel does.

I'm not sure if there is a reason that Laravel Zero does this, but a solution we can propose (and also apply to your repo) is:

  • Use env('APP_ENV', 'development') in config/app.php.
  • Add <env name="APP_ENV" value="testing"/> to phpunit.xml.dist

With these changes in place, your tests pass.

Awesome, thank you for the working solution and detailed explanation @jessarcher

chrispage1 commented 8 months ago

@sc85 @jessarcher

I've run into exactly the same problem using standard Laravel with all packages up to date. I've had to amend my asserts to

$this->artisan(GenerateTranslations::class, ['filename' => 'en.messages'])
        ->expectsQuestion('Choose your target language', 'JA')
        ->expectsChoice('Choose your target language', 'JA', ['JA', 'Japanese'])

I've checked app()->runningInUnitTests() and that returns true. So for me it actually makes more sense to add a conditional and ask a standard question rather than a search.

Here's my code snippet:

$availableLanguages = collect(Language::cases())->mapWithKeys(
            fn (Language $language) => [$language->value => $language->name]
        );

        if (app()->runningUnitTests()) {
            $targetLanguage = Language::tryFrom($this->ask('Choose your target language'));
        } else {
            $targetLanguage = Language::tryFrom(
                search(
                    'Choose your target language',
                    fn ($value) => $availableLanguages
                        ->except($sourceLanguage->value)
                        ->when($value, fn (Collection $collection, string $value) => $collection->filter(
                            fn ($language) => str_contains(strtolower($language), strtolower($value))
                        ))
                        ->toArray(),
                )
            );
        }

Without this tests fail with a Mock exception Mockery\Exception\InvalidCountException: Method askQuestion(<Closure===true>) from Mockery_2_Illuminate_Console_OutputStyle should be called