yiisoft / yii2-app-basic

Yii 2.0 Basic Application Template
http://www.yiiframework.com
BSD 3-Clause "New" or "Revised" License
657 stars 789 forks source link

UrlManager::buildRules() called with a boolean instead of array when unit testing #306

Closed cyril-amar closed 1 year ago

cyril-amar commented 1 year ago

What steps will reproduce the problem?

  1. Create a custom module, implementing yii\base\BootstrapInterface and extending an abstract class itself extending yii\base\Module (only adding parameters/methods, not surcharging any)
  2. Add pretty URLs from the Module::bootstrap() method
    $app->getUrlManager()->addRules([
            [
                'verb'    => 'GET',
                'pattern' => '/org/config/incident/severity',
                'route'   => '/incident/severity-level/index',
            ],
            [
                'verb'    => 'GET,POST',
                'pattern' => '/org/config/incident/severity/new',
                'route'   => '/incident/severity-level/create',
            ],
            [
                'verb'    => 'GET,POST',
                'pattern' => '/org/config/incident/severity/<id:[0-9]+>/<action:(update|delete)>',
                'route'   => '/incident/severity-level/<action>',
            ],
        ], false);
  3. Add the module in Yii::$app->bootstrap in the config file used by testing.
  4. Run unit tests $> vendor/bin/codecept run unit

What's expected?

Tests are run.

What do you get instead?

First test in run OK, all others are met with:

  [yii\base\ErrorException] foreach() argument must be of type array|object, bool given  

#1  /home/clients/<my_customer_path>/vendor/yiisoft/yii2/web/UrlManager.php:241
#2  /home/clients/<my_customer_path>/vendor/yiisoft/yii2/web/UrlManager.php:193
#3  /home/clients/<my_customer_path>/vendor/yiisoft/yii2/base/BaseObject.php:109
#4  yii\base\BaseObject->__construct
#5  /home/clients/<my_customer_path>/vendor/yiisoft/yii2/di/Container.php:419
#6  /home/clients/<my_customer_path>/vendor/yiisoft/yii2/di/Container.php:170
#7  /home/clients/<my_customer_path>/vendor/yiisoft/yii2/BaseYii.php:365
#8  /home/clients/<my_customer_path>/vendor/yiisoft/yii2/di/ServiceLocator.php:137
#9  /home/clients/<my_customer_path>/vendor/yiisoft/yii2/base/Module.php:766
#10 /home/clients/<my_customer_path>/vendor/yiisoft/yii2/base/Application.php:569

Additional info

Running the app while not in unit testing works fine. The custom module is only bootstrapped, not tested (yet!).

I determined that UrlManager::buildRules() in yii2/web/UrlManager.php:228 is called with true instead of an array (viewed by dumping the value). Declaring UrlManager::$rules as an array on UrlManager.php:105 provokes indeed an error:

TypeError: Cannot assign bool to property yii\web\UrlManager::$rules of type array in /home/clients/<my_customer_path>/vendor/yiisoft/yii2/BaseYii.php:558
Stack trace:
#0 /home/clients/<my_customer_path>/vendor/yiisoft/yii2/base/BaseObject.php(107): yii\BaseYii::configure(Object(yii\web\UrlManager), Array)
#1 [internal function]: yii\base\BaseObject->__construct(Array)
#2 /home/clients/<my_customer_path>/vendor/yiisoft/yii2/di/Container.php(419): ReflectionClass->newInstanceArgs(Array)
#3 /home/clients/<my_customer_path>/vendor/yiisoft/yii2/di/Container.php(170): yii\di\Container->build('yii\\web\\UrlMana...', Array, Array)
#4 /home/clients/<my_customer_path>/vendor/yiisoft/yii2/BaseYii.php(365): yii\di\Container->get('yii\\web\\UrlMana...', Array, Array)
#5 /home/clients/<my_customer_path>/vendor/yiisoft/yii2/di/ServiceLocator.php(137): yii\BaseYii::createObject(Array)
#6 /home/clients/<my_customer_path>/vendor/yiisoft/yii2/base/Module.php(766): yii\di\ServiceLocator->get('urlManager', true)
#7 /home/clients/<my_customer_path>/vendor/yiisoft/yii2/base/Application.php(569): yii\base\Module->get('urlManager')
#8 /home/clients/<my_customer_path>/modules/incident/Module.php(69): yii\base\Application->getUrlManager()
#9 /home/clients/<my_customer_path>/vendor/yiisoft/yii2/base/Application.php(325): app\modules\incident\Module->bootstrap(Object(yii\web\Application))
#10 /home/clients/<my_customer_path>/vendor/yiisoft/yii2/web/Application.php(69): yii\base\Application->bootstrap()
#11 /home/clients/<my_customer_path>/vendor/yiisoft/yii2/base/Application.php(271): yii\web\Application->bootstrap()
#12 /home/clients/<my_customer_path>/vendor/yiisoft/yii2/base/BaseObject.php(109): yii\base\Application->init()
#13 /home/clients/<my_customer_path>/vendor/yiisoft/yii2/base/Application.php(204): yii\base\BaseObject->__construct(Array)
#14 [internal function]: yii\base\Application->__construct(Array)
#15 /home/clients/<my_customer_path>/vendor/yiisoft/yii2/di/Container.php(419): ReflectionClass->newInstanceArgs(Array)
#16 /home/clients/<my_customer_path>/vendor/yiisoft/yii2/di/Container.php(170): yii\di\Container->build('yii\\web\\Applica...', Array, Array)
#17 /home/clients/<my_customer_path>/vendor/yiisoft/yii2/BaseYii.php(365): yii\di\Container->get('yii\\web\\Applica...', Array, Array)
#18 /home/clients/<my_customer_path>/vendor/codeception/module-yii2/src/Codeception/Lib/Connector/Yii2.php(299): yii\BaseYii::createObject(Array)
#19 /home/clients/<my_customer_path>/vendor/codeception/module-yii2/src/Codeception/Module/Yii2.php(329): Codeception\Lib\Connector\Yii2->startApp(Object(Codeception\Lib\Connector\Yii2\Logger))
#20 /home/clients/<my_customer_path>/vendor/codeception/codeception/src/Codeception/Subscriber/Module.php(70): Codeception\Module\Yii2->_before(Object(Codeception\Test\TestCaseWrapper))
#21 /home/clients/<my_customer_path>/vendor/symfony/event-dispatcher/EventDispatcher.php(220): Codeception\Subscriber\Module->before(Object(Codeception\Event\TestEvent), 'test.before', Object(Symfony\Component\EventDispatcher\EventDispatcher))
#22 /home/clients/<my_customer_path>/vendor/symfony/event-dispatcher/EventDispatcher.php(56): Symfony\Component\EventDispatcher\EventDispatcher->callListeners(Array, 'test.before', Object(Codeception\Event\TestEvent))
#23 /home/clients/<my_customer_path>/vendor/codeception/codeception/src/Codeception/Test/Test.php(296): Symfony\Component\EventDispatcher\EventDispatcher->dispatch(Object(Codeception\Event\TestEvent), 'test.before')
#24 /home/clients/<my_customer_path>/vendor/codeception/codeception/src/Codeception/Test/Test.php(149): Codeception\Test\Test->fire('test.before', Object(Codeception\Event\TestEvent))
#25 /home/clients/<my_customer_path>/vendor/codeception/codeception/src/Codeception/Suite.php(130): Codeception\Test\Test->realRun(Object(Codeception\ResultAggregator))
#26 /home/clients/<my_customer_path>/vendor/codeception/codeception/src/Codeception/SuiteManager.php(148): Codeception\Suite->run(Object(Codeception\ResultAggregator))
#27 /home/clients/<my_customer_path>/vendor/codeception/codeception/src/Codeception/Codecept.php(260): Codeception\SuiteManager->run(Object(Codeception\ResultAggregator))
#28 /home/clients/<my_customer_path>/vendor/codeception/codeception/src/Codeception/Codecept.php(216): Codeception\Codecept->runSuite(Array, 'unit', NULL)
#29 /home/clients/<my_customer_path>/vendor/codeception/codeception/src/Codeception/Command/Run.php(646): Codeception\Codecept->run('unit')
#30 /home/clients/<my_customer_path>/vendor/codeception/codeception/src/Codeception/Command/Run.php(467): Codeception\Command\Run->runSuites(Array, Array)
#31 /home/clients/<my_customer_path>/vendor/symfony/console/Command/Command.php(326): Codeception\Command\Run->execute(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#32 /home/clients/<my_customer_path>/vendor/symfony/console/Application.php(1063): Symfony\Component\Console\Command\Command->run(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#33 /home/clients/<my_customer_path>/vendor/symfony/console/Application.php(320): Symfony\Component\Console\Application->doRunCommand(Object(Codeception\Command\Run), Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#34 /home/clients/<my_customer_path>/vendor/symfony/console/Application.php(174): Symfony\Component\Console\Application->doRun(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#35 /home/clients/<my_customer_path>/vendor/codeception/codeception/src/Codeception/Application.php(112): Symfony\Component\Console\Application->run(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#36 /home/clients/<my_customer_path>/vendor/codeception/codeception/app.php(45): Codeception\Application->run()
#37 /home/clients/<my_customer_path>/vendor/codeception/codeception/app.php(46): {closure}()
#38 /home/clients/<my_customer_path>/vendor/codeception/codeception/codecept(7): require('/home/clients/5...')
#39 /home/clients/<my_customer_path>/vendor/bin/codecept(120): include('/home/clients/5...')
#40 {main}
Q A
Yii version 2.0.48.1
PHP version 8.2
Operating system Linux, specifics unavailable
particleflux commented 1 year ago

Cannot reproduce in basic app.

Created a module using:

./yii gii/module --moduleClass 'app\modules\Test\Module' --moduleID test

Edited it to implement BootstrapInterface and add custom url rules on bootstrap:

<?php

namespace app\modules\Test;

use yii\base\BootstrapInterface;

/**
 * test module definition class
 */
class Module extends \yii\base\Module implements BootstrapInterface
{
    /**
     * {@inheritdoc}
     */
    public $controllerNamespace = 'app\modules\Test\controllers';

    /**
     * {@inheritdoc}
     */
    public function init()
    {
        parent::init();

        // custom initialization code goes here
    }

    public function bootstrap($app)
    {
        $app->getUrlManager()->addRules([
            [
                'verb'    => 'GET',
                'pattern' => '/org/config/incident/severity',
                'route'   => '/incident/severity-level/index',
            ],
            [
                'verb'    => 'GET,POST',
                'pattern' => '/org/config/incident/severity/new',
                'route'   => '/incident/severity-level/create',
            ],
            [
                'verb'    => 'GET,POST',
                'pattern' => '/org/config/incident/severity/<id:[0-9]+>/<action:(update|delete)>',
                'route'   => '/incident/severity-level/<action>',
            ],
        ], false);
    }
}

Added to config/test.php:

    'bootstrap' => [
        'class' => app\modules\Test\Module::class,
    ],
    'modules'             => [
        'admin'     => [
            'class' => app\modules\Test\Module::class
        ],
    ]

Tests run fine. (both unit tests and full test suites, including functional)

Any more info you can give? Or maybe a minimal reproduction project in a zip?

Edit: tested with current master version

codeception/codeception       5.0.x-dev 2f06902  BDD-style testing framework
codeception/lib-innerbrowser  4.0.0              Parent library for all Codeception framework modules and PhpBrowser
codeception/module-asserts    3.0.0              Codeception module containing various assertions
codeception/module-filesystem 3.0.0              Codeception module for testing local filesystem
codeception/module-yii2       1.1.9              Codeception module for Yii2 framework
codeception/verify            3.0.0              BDD assertion library for PHPUnit
symfony/browser-kit           6.4.x-dev 3f5752f  Simulates the behavior of a web browser, allowing you to make requests, click on links and submit forms programmatically
yiisoft/yii2                  dev-master ddf0f87 Yii PHP Framework Version 2
yiisoft/yii2-bootstrap5       dev-master cf7807d The Twitter Bootstrap v5 extension for the Yii framework
yiisoft/yii2-debug            2.1.25             The debugger extension for the Yii framework
yiisoft/yii2-faker            dev-master 98e4e4c Fixture generator. The Faker integration for the Yii framework.
yiisoft/yii2-gii              2.2.6              The Gii extension for the Yii framework
yiisoft/yii2-symfonymailer    2.0.4              The SymfonyMailer integration for the Yii framework
cyril-amar commented 1 year ago

Hi,

Thanks!

I should have had added something indeed, my bad :( The issue only occurs if pretty URLs are enabled

'urlManager'   => [
            'enablePrettyUrl'     => true,
            'showScriptName'      => false,
            'enableStrictParsing' => true,
            'rules'               => $urls, // Declared rules for the "core" app, without modules
        ],

Switching enablePrettyUrl to false solves the issue and allows tests to run. Commenting out all the rules definition in Module.php also works fine.

I'm running on

codeception/codeception            5.0.11             BDD-style testing framework                                                                   
codeception/lib-asserts            2.1.0              Assertion methods used by Codeception core and Asserts module                                 
codeception/lib-innerbrowser       3.1.3              Parent library for all Codeception framework modules and PhpBrowser                           
codeception/lib-web                1.0.2              Library containing files used by module-webdriver and lib-innerbrowser or module-phpbrowser   
codeception/module-asserts         3.0.0              Codeception module containing various assertions                                              
codeception/module-db              3.1.0              DB module for Codeception                                                                     
codeception/module-filesystem      3.0.0              Codeception module for testing local filesystem                                               
codeception/module-yii2            1.1.9              Codeception module for Yii2 framework                                                         
codeception/stub                   4.1.1              Flexible Stub wrapper for PHPUnit's Mock Builder                                              
codeception/verify                 3.0.0              BDD assertion library for PHPUnit                                                             
phpunit/php-code-coverage          9.2.29             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.13             The PHP Unit Testing framework.
symfony/browser-kit                v6.3.2             Simulates the behavior of a web browser, allowing you to make requests, click on links and submit forms programmatically
yiisoft/yii2                       2.0.49.1           Yii PHP Framework Version 2
yiisoft/yii2-bootstrap4            2.0.11             The Twitter Bootstrap extension for the Yii framework
yiisoft/yii2-bootstrap5            2.0.4              The Twitter Bootstrap v5 extension for the Yii framework
yiisoft/yii2-composer              2.0.10             The composer plugin for Yii extension installer
yiisoft/yii2-debug                 2.1.25             The debugger extension for the Yii framework
yiisoft/yii2-faker                 2.0.5              Fixture generator. The Faker integration for the Yii framework.
yiisoft/yii2-gii                   2.2.6              The Gii extension for the Yii framework
yiisoft/yii2-symfonymailer         3.0.0              The SymfonyMailer integration for the Yii framework
[...]

I'll try to reproduce in a brand new project and share it.

cyril-amar commented 1 year ago

I cannot reproduce either in basic-app for now, seems I have something else elsewhere. I'm still working on identifying the culprit.