Home > Enterprise >  Unable to clear old errors from MockSession when running multiple tests in CodeIgniter4/PHP
Unable to clear old errors from MockSession when running multiple tests in CodeIgniter4/PHP

Time:06-21

I am using the Codeigniter4 framework and I have a controller function that displays a form with name, email, and message fields and then on submission validates the input. I am trying to set up 2 test cases using the built-in PHPUnit test suite for this controller: one with all errors and one with no errors. The issue is that after I run the first test with the errors, the second test always fails with the same errors being saved. I have tried destroying the session, but because the test suite uses MockSession, the destroy() doesn't seem to work the same.

Here is the function that displays the form:

public function question()
{
    $items = array(
        "name"          => array("display" => "Your Name", "type" => "input"),
        "email"         => array("display" => "Your Email", "type" => "input"),
        "message"       => array("display" => "Message", "type" => "textarea", "rows" => 5),
    );

    $data = $this->getForm($items, array(), "How Can We Help?");
    $this->displayView("template/form", $data);
}

Here is the function that validates input after form submission, displays message to user, and redirect back to form:

public function attemptQuestion()
{
    $rules = array(
        "name"          => array(
            "rules" => "required|string|max_length[30]",
            "errors" => array(
                "required" => "Your name is required",
                "max_length" => "Please limit to the first 30 characters in your name"
            )
        ),
        "email"         => array(
            "rules" => "required|valid_email|max_length[30]",
            "errors" => array(
                "required" => "Your email is required",
                "valid_email" => "Your email is invalid",
                "max_length" => "Email address is limited to 30 characters"
            )
        ),
        "message"           => array(
            "rules" => "required",
            "errors" => array(
                "required" => "Message is required"
            )
        ),
    );

    if (! $this->validate($rules))
    {
        return redirect()->to(site_url("contact/question"))->withInput()->with("errors", $this->validator->getErrors());
    }
    return redirect()->to(site_url("contact/question"))->with("message", lang("Base.messageSent"));
}

And here is the test class:

namespace CodeIgniter;

use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\Test\DatabaseTestTrait;
use CodeIgniter\Test\FilterTestTrait;
use CodeIgniter\Test\FeatureTestTrait;

class QuestionTest extends CIUnitTestCase
{
    use FilterTestTrait;

    public function testQuestionError()
    {
        $params = array(
            "name" => "01234567890123456789012345678901234567", // >30 characters
            "email" => "01234567890123456789012345678901234567", // >30 characters, invalid email
            "message" => NULL, // empty
            csrf_token() => csrf_hash()
        );

        $errors = array(
            'name' => 'Please limit to the first 100 characters in your name',
            'email' => 'Your email is invalid',
            'message' => 'Message is required'
        );

        $this->checkIndex($params, $errors);
    }

    public function testQuestionSuccess()
    {
        $params = array(
            "name" => "Joe Tester",
            "email" => "[email protected]",
            "message" => "this is a test",
            csrf_token() => csrf_hash()
        );

        $this->checkIndex($params, FALSE, lang("Base.messageSent"));
    }

    private function checkQuestion($parameters, $errors = FALSE, $message = FALSE)
    {
        // clear session
        $_SESSION = [];

        $result = $this->post("contact/index", $parameters);

        $result->assertOK();

        $result->assertRedirect();

        $result->assertRedirectTo(site_url("contact/question"));

        if ($errors)
        {
            $result->assertSessionHas('errors', $errors);
        }
        if ($message)
        {
            $result->assertSessionHas('message', $message);
        }
    }
}

For reference, here are a couple links that are relevant, but still don't seem to fully answer the question:

https://github.com/codeigniter4/CodeIgniter4/issues/3578

https://forum.codeigniter.com/thread-74701.html

UPDATE

I've upgraded to CI4.2.0 which enables "resetServices" by default, which should fix the issue, but unfortunately, I get the same error. For reference, I've tried wrapping the $this->post call in print statements, like this:

        file_put_contents('php://stderr', print_r($parameters, true));
        $result = $this->post("contact/index", $parameters);
        print_r($_SESSION);

And that produces the following results:

evana@LAPTOP-ICCPFR27 MINGW64 /c/sites/www (master)
$ ./vendor/bin/phpunit --filter 'ContactPostTest'
PHPUnit 9.5.20 #StandWithUkraine

Warning:       No code coverage driver available

Array
(
    [name] => 01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
    [email] => 01234567890123456789012345678901234567890123456789012345678901234567890123456789089@example.com
    [subject] =>
    [csrfPEtoken] => 928d217bb8d905caa5783275a72347db
)
.Array
(
    [_ci_old_input] => Array
        (
            [get] => Array
                (
                )

            [post] => Array
                (
                )

        )

    [__ci_vars] => Array
        (
            [_ci_old_input] => new
            [_ci_validation_errors] => new
            [errors] => new
        )

    [_ci_validation_errors] => a:6:{s:4:"name";s:53:"Please limit to the first 100 characters in your name";s:5:"email";s:21:"Your email is invalid";s:7:"message";s:19:"Message is required";}
    [errors] => Array
        (
            [name] => Please limit to the first 100 characters in your name
            [email] => Your email is invalid
            [message] => Message is required
        )

)
Array
(
    [name] => Evan
    [email] => [email protected]
    [message] => this is a test
    [csrfPEtoken] => a55091ab0a922566c44b3ede325b59cd
)
F                                                                  2 / 2 (100%)Array
(
    [_ci_old_input] => Array
        (
            [get] => Array
                (
                )

            [post] => Array
                (
                )

        )

    [__ci_vars] => Array
        (
            [_ci_old_input] => new
            [_ci_validation_errors] => new
            [errors] => new
        )

    [_ci_validation_errors] => a:6:{s:4:"name";s:53:"Please limit to the first 100 characters in your name";s:5:"email";s:21:"Your email is invalid";s:7:"message";s:19:"Message is required";}
    [errors] => Array
        (
            [name] => Please limit to the first 100 characters in your name
            [email] => Your email is invalid
            [message] => Message is required
        )

)

Time: 00:00.320, Memory: 16.00 MB

There was 1 failure:

1) CodeIgniter\ContactPostTest::testQuestionSuccess
'message' is not in the current $_SESSION
Failed asserting that an array has the key 'message'.

C:\sites\www\vendor\codeigniter4\framework\system\Test\TestResponse.php:255
C:\sites\www\tests\app\Controllers\ContactPostTest.php:101
C:\sites\www\tests\app\Controllers\ContactPostTest.php:67

FAILURES!
Tests: 2, Assertions: 7, Failures: 1.

CodePudding user response:

To clear out the session, you need to use MockSession and enable resetServices. Also, in case it is helpful to anyone else, you need have only one HTTP request in each function (when I expanded my tests, I put all the error calls in one function, which caused issues with the session as well).

Here is the corrected code:

<?php

namespace CodeIgniter;

use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\Test\FilterTestTrait;
use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Session\Handlers\ArrayHandler;
use CodeIgniter\Test\Mock\MockSession;
use Config\Services;

class ContactPostTest extends CIUnitTestCase
{
    use FilterTestTrait, FeatureTestTrait;

    protected $setUpMethods = [
        'resetServices',
        'mockSession',
    ];

    public function testQuestionError()
    {
        $params = array(
            "name" => "01234567890123456789012345678901234567", // >30 characters
            "email" => "01234567890123456789012345678901234567", // >30 characters, invalid email
            "message" => NULL, // empty
            csrf_token() => csrf_hash()
        );

        $errors = array(
            'name' => 'Please limit to the first 100 characters in your name',
            'email' => 'Your email is invalid',
            'message' => 'Message is required'
        );

        $this->checkQuestion($params, $errors);
    }

    public function testQuestionSuccess()
    {
        $params = array(
            "name" => "Joe Tester",
            "email" => "[email protected]",
            "message" => "this is a test",
            csrf_token() => csrf_hash()
        );

        $this->checkQuestion($params, FALSE, lang("Base.messageSent"));
    }

    private function checkQuestion($parameters, $errors = FALSE, $message = FALSE)
    {
        $result = $this->post("contact", $parameters);

        $result->assertOK();

        $result->assertRedirect();

        $result->assertRedirectTo(site_url("contact"));

        if ($errors)
        {
            $result->assertSessionHas('errors', $errors);
        }
        if ($message)
        {
            $result->assertSessionHas('message', $message);
        }
    }
}
  • Related