Codeception / module-yii2

Codeception module for Yii2 framework
MIT License
16 stars 36 forks source link

A mix of an old and a new data after submitForm() #9

Closed DiscipleOfShinku closed 4 years ago

DiscipleOfShinku commented 4 years ago

What are you trying to achieve?

To test an entry editing.

tests/functional/AdminCest.php

class AdminCest
{
    public function _before(FunctionalTester $I)
    {
        $I->amLoggedInAs(1);
    }

    public function editAdminCredentials(FunctionalTester $I)
    {
        $I->amOnRoute('admin/edit?id=1');
        $I->submitForm('#admin-form', [
            'User[username]' => 'test-edited',
            'User[password]' => 'test-edited',
        ]);
        $I->see('Exit (test-edited)');
    }
}

What do you get instead?

I get old Yii::$app->user->identity->username and old breadcrumbs.

1) AdminCest: Edit admin credentials
 Test  tests/functional/AdminCest.php:editAdminCredentials
 Step  See "Exit (test-edited)"
 Fail  Failed asserting that  on page /admin/view?id=1
-->  test-edited Toggle navigation Home News Admins Users URL Exit (test) Admin panel Admins test Admins test-edited test-edited Edit
--> contains "Exit (test-edited)".

tests/_output/AdminCest.editAdminCredentials.fail.html

<!DOCTYPE html>
<html lang="en-US">
<head>
    <meta charset="UTF-8">
        <title>test-edited</title>
<body>

    <div class="wrap">
        <nav id="w41" class="navbar-inverse navbar-fixed-top navbar"><div class="container"><div class="navbar-header"><button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#w41-collapse"><span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span></button><a class="navbar-brand" href="/"><img id='logo' src='images/logo.png' /></a></div><div id="w41-collapse" class="collapse navbar-collapse"><ul id="w42" class="navbar-nav navbar-right nav"><li><a href="/site">Home</a></li>
<li><a href="/news">News</a></li>
<li><a href="/admin">Admins</a></li>
<li><a href="/user">Users</a></li>
<li><a href="/download-url">URL</a></li>
<li><form action="/site/logout" method="post"><button type="submit" class="btn btn-link logout">Exit (test)</button></form></li></ul></div></div></nav>
        <div class="container">
            <ul class="breadcrumb"><li><a href="/">Admin panel</a></li>
<li><a href="/admin">Admins</a></li>
<li class="active">test</li>
<li><a href="/admin">Admins</a></li>
<li class="active">test-edited</li>
</ul>                        <div>
    <h1>test-edited</h1>

    <div class="row col-lg-6">

        <form id="w40" action="/admin/edit" method="get">
<input type="hidden" name="id" value="1">
            <div class="form-group">
                <button type="submit" class="btn btn-primary">Edit</button>            </div>

        </form>
    </div>
</div>
        </div>
    </div>
</body>
</html>

controllers/AdminController.php

    public function actionEdit(int $id)
    {
        $model = User::findOne($id);
        if (!$model) {
            throw new NotFoundHttpException();
        }

        if ($model->load(Yii::$app->request->post()) && $model->save()) {
            return $this->redirect(['/admin/view?id=' . $model->id]);
        }

        $params = ['model' => $model, 'title' => $model->username];

        return $this->render('edit', $params);
    }

views/layout/main.php

/* @var $this \yii\web\View */
/* @var $content string */

use app\widgets\Alert;
use yii\helpers\Html;
use yii\bootstrap\Nav;
use yii\bootstrap\NavBar;
use yii\widgets\Breadcrumbs;
use app\assets\AppAsset;

AppAsset::register($this);
?>

<?php $this->beginPage() ?>
<!DOCTYPE html>
<html lang="<?= Yii::$app->language ?>">
<head>
    <meta charset="<?= Yii::$app->charset ?>">
    <title><?= Html::encode($this->title) ?></title>
    <?php $this->head() ?>
</head>
<body>
<?php $this->beginBody() ?>

<?php if (Yii::$app->user->isGuest): ?>
    <?= $content ?>
<?php else: ?>
    <div class="wrap">
        <?php
        NavBar::begin([
            'brandLabel' => "<img id='logo' src='images/logo.png' />",
            'brandUrl' => Yii::$app->homeUrl,
            'options' => [
                'class' => 'navbar-inverse  navbar-fixed-top',
            ],
        ]);
        echo Nav::widget([
            'options' => ['class' => 'navbar-nav navbar-right'],
            'items' => [
                ['label' => 'Home', 'url' => ['/site']],
                ['label' => 'News', 'url' => ['/news']],
                ['label' => 'Admins', 'url' => ['/admin']],
                ['label' => 'Users', 'url' => ['/user']],
                ['label' => 'URL', 'url' => ['/download-url']],
                (
                    '<li>'
                    . Html::beginForm(['/site/logout'], 'post')
                    . Html::submitButton(
                        'Exit (' . Yii::$app->user->identity->username . ')',
                        ['class' => 'btn btn-link logout']
                    )
                    . Html::endForm()
                    . '</li>'
                ),
            ],
        ]);
        NavBar::end();
        ?>

        <div class="container">
            <?= Breadcrumbs::widget([
                'homeLink' => [
                    'label' => 'Admin panel',
                    'url' => Yii::$app->homeUrl,
                ],
                'links' => isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [],
            ]) ?>
            <?= Alert::widget() ?>
            <?= $content ?>
        </div>
    </div>
 <?php endif; ?>

<?php $this->endBody() ?>
</body>
</html>
<?php $this->endPage() ?>

views/admin/edit.php

/* @var $this yii\web\View */
/* @var $form yii\bootstrap\ActiveForm */
/* @var $model app\models\User */

use yii\bootstrap\ActiveForm;
use yii\helpers\Html;

$this->title = $title;

$this->params['breadcrumbs'][] = [
    'label' => 'Admins',
    'url' => '/admin',
];
$this->params['breadcrumbs'][] = $title;

?>
<div>
    <h1><?= Html::encode($title) ?></h1>

    <div class="row col-lg-4">

        <?php $form = ActiveForm::begin(['id' => 'admin-form']); ?>

            <?= $form->field($model, 'username')->textInput(['autofocus' => true]) ?>

            <?= $form->field($model, 'password')->passwordInput(['value' => '']) ?>

            <div class="form-group">
                <?= Html::submitButton('Save', ['class' => 'btn btn-primary']) ?>
            </div>

        <?php ActiveForm::end(); ?>

    </div>
</div>

views/admin/view.php

/* @var $this yii\web\View */
/* @var $model app\models\User */

use yii\bootstrap\ActiveForm;
use yii\helpers\Html;

$this->title = $model->username;

$this->params['breadcrumbs'][] = [
    'label' => 'Admins',
    'url' => '/admin',
];
$this->params['breadcrumbs'][] = $model->username;

?>
<div>
    <h1><?= Html::encode($model->username) ?></h1>

    <div class="row col-lg-6">

        <?php $form = ActiveForm::begin([
            'method' => 'get',
            'action' => ['/admin/edit?id=' . $model->id],
        ]); ?>

            <div class="form-group">
                <?= Html::submitButton('Edit', ['class' => 'btn btn-primary']) ?>
            </div>

        <?php ActiveForm::end(); ?>

    </div>
</div>

Details

class_name: FunctionalTester
modules:
    enabled:
        - Yii2
        - Db:
            dsn: 'mysql:host=localhost;dbname=db_test'
            user: 'root'
            password:
            cleanup: true
            populate: true
            populator: 'mysql -u $user $dbname < tests/_data/dump.sql'
Naktibalda commented 4 years ago

Are you sure that it isn't a bug of your application? Does it display expected text if tested manually?

DiscipleOfShinku commented 4 years ago

Yes, in a browser all works as expected.

DiscipleOfShinku commented 4 years ago

This test worked with Codeception 2.3.9.

SamMousa commented 4 years ago

Please run this test with --debug and provide the output!

DiscipleOfShinku commented 4 years ago
[Connecting To Db] {"config":{"populate":true,"cleanup":true,"reconnect":false,"waitlock":0,"dump":null,"populator":"mysql -u $user $dbname < tests/_data/dump.sql","dsn":"mysql:host=localhost;dbname=db_test","user":"root","password":null},"options":[]}
[Db] Connected to default db_test
[Db] Executing Populator: `mysql -u root db_test < tests/_data/dump.sql`
[Db] Populator Finished.

Functional Tests (13) ---------------------------------------------------------------------------------------------------------------------------------------------------
Modules: Yii2, Db
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
AdminCest: Edit admin credentials
Signature: AdminCest:editAdminCredentials
Test: tests/functional/AdminCest.php:editAdminCredentials
Scenario --
Destroying application
Starting application
[ConnectionWatcher] watching new connections
[Fixtures] Loading fixtures
[Fixtures] Done
[TransactionForcer] watching new connections
[Db] Executing Populator: `mysql -u root db_test < tests/_data/dump.sql`
[Db] Populator Finished.
I am logged in as 1
[application] 'yii\\web\\Application#1
(
  // many lines here
)'

  [yii\db\Connection::open] 'Opening DB connection: mysql:host=localhost;dbname=db_test'
  [ConnectionWatcher] Connection opened!
  [TransactionForcer] Connection opened!
  [TransactionForcer] Transaction started for: mysql:host=localhost;dbname=db_test
  [yii\web\Session::open] 'Session started'
  [yii\web\User::login] 'User \'1\' logged in from  with duration 0.'                                   
 I am on route "admin/edit?id=1"
  [Request Headers] []
  [Page] /admin/edit?id=1
  [Response] 200
  [Request Cookies] []
  [Response Headers] {"content-type":["text/html; charset=UTF-8"]}
 I submit form "#admin-form",{"User[username]":"test-edited","User[password]":"test-edited"}
  [Uri] http://localhost/admin/edit?id=1
  [Method] POST
  [Parameters] {"User[username]":"test-edited","User[password]":"test-edited"}
  [Request Headers] []
  [Redirect with headers]Array
  (
      [location] => Array
          (
              [0] => http://localhost/admin/view?id=1
          )

      [content-type] => Array
          (
              [0] => text/html; charset=UTF-8
          )

  )

  [Page] http://localhost/admin/edit?id=1
  [Response] 302
  [Request Cookies] []
  [Response Headers] {"location":["http://localhost/admin/view?id=1"],"content-type":["text/html; charset=UTF-8"]}
  [Redirecting to] http://localhost/admin/view?id=1
  [Page] http://localhost/admin/view?id=1
  [Response] 200
  [Request Cookies] []
  [Response Headers] {"content-type":["text/html; charset=UTF-8"]}
 I see "Exit (test-edited)"
 FAIL

  [TransactionForcer] Transaction cancelled; all changes reverted.
  [TransactionForcer] no longer watching new connections
  Destroying application
  [ConnectionWatcher] no longer watching new connections
  [ConnectionWatcher] closing all (1) connections
  Suite done, restoring $_SERVER to 
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
DEPRECATION: 'settings: bootstrap: _bootstrap.php' option is deprecated! Replace it with: 'bootstrap: _bootstrap.php' (not under settings section). See https://bit.ly/2YrRzVc
  [Connecting To Db] {"config":{"populate":true,"cleanup":true,"reconnect":false,"waitlock":0,"dump":null,"populator":"mysql -u $user $dbname < tests/_data/dump.sql","dsn":"mysql:host=localhost;dbname=db_test","user":"root","password":null},"options":[]}
  [Db] Connected to default db_test
  [Db] Executing Populator: `mysql -u root db_test < tests/_data/dump.sql`
  [Db] Populator Finished.
Naktibalda commented 4 years ago

@SamMousa Debug output was provided, are you going to look at it again?

SamMousa commented 4 years ago

I'll put it in the list

SamMousa commented 4 years ago

I've taken a look and nothing stands out to me. If you feel this is a bug in codeception please provide a minimal test case, ideally as a PR for the test repo.

DiscipleOfShinku commented 4 years ago

Sorry, I don't know about the test repo. Here is a minimal test case. It requires a DB and includes a migration for it.

SamMousa commented 4 years ago

@DiscipleOfShinku thanks for that, it explains a lot.

What is happening is a different issue. In Yii's functional test module we do not reset the whole application during a test. This allows you to inspect the application state after / before a request.

In your case, the User component loads your identity on the first page load. (Since it is used there to render the header). On the 2nd render, which happens after updating, the information is not reloaded since it is already available.

There is actually configuration that allows you to prevent this: https://codeception.com/docs/modules/Yii2#Config, look for the recreateComponents option. Add the user component to it and we'll make sure that it's reinitialized upon every request.

If you cannot get that to work, you could also try this hacky fix for debugging only:


public function _before(FunctionalTester $I)
    {
        $I->amLoggedInAs(1);
    }

    public function editAdminCredentials(FunctionalTester $I)
    {
        $I->amOnRoute('admin/edit?id=1');
        $I->submitForm('#admin-form', [
            'User[username]' => 'test-edited',
            'User[password]' => 'test-edited',
        ]);
        /**
          * BEGIN UGLY HACK
          * Calling this will force the user component to refresh it's data.
        */
        $I->amLoggedInAs(1);
        $I->amOnRoute('admin/edit?id=1');
        /** END UGLY HACK */

        $I->see('Exit (test-edited)');
    }
DiscipleOfShinku commented 4 years ago

Oh! I probably should have read this option's description several times. And there are two requests in that test.

recreateComponent: ['user'] fixed the test.

Thank you and sorry for the trouble.