Laravel and Codeception - test fails on PUT forms

2019-05-14 03:23发布

问题:

I have a resource controller in laravel to manage my users. This creates a route to update users info that takes a request with HTTP PUT method.

This shows artisan route:list command output:

+--------+--------------------------------+-----------------------------------------------------------+--------------------------------------+--------------------------------------------------------------------------------+------------+
| Domain | Method                         | URI                                                       | Name                                 | Action                                                                         | Middleware |
+--------+--------------------------------+-----------------------------------------------------------+--------------------------------------+--------------------------------------------------------------------------------+------------+
...
|        | PUT                            | users/{users}                                             | users.update                         | App\Http\Controllers\Users\UsersController@update                              | auth       |

It works correctly on my web browser but when I try to run a test with codeception and I submit the form I get a method not allowed exception and the test fails.

I tried to see why this is happening and it seems to be the request made by codeception. That request is made with POST instead of PUT method preventing Laravel from matching the route.

HTML forms doesn't suport PUT methods so Laravel Form helper class creates the form as follows:

<form method="POST" action="https://myapp.dev/users/172" accept-charset="UTF-8">
    <input name="_method" value="PUT" type="hidden">
...

However, it seems that codeception is not reading the _method value.

How can I fix this?

EDIT: Looking deeply on the code I found that test don't override the request method beacause of a constant in th Request class called Request::$httpMethodParameterOverride.

/**
 * Gets the request "intended" method.
 *
 * If the X-HTTP-Method-Override header is set, and if the method is a POST,
 * then it is used to determine the "real" intended HTTP method.
 *
 * The _method request parameter can also be used to determine the HTTP method,
 * but only if enableHttpMethodParameterOverride() has been called.
 *
 * The method is always an uppercased string.
 *
 * @return string The request method
 *
 * @api
 *
 * @see getRealMethod()
 */
public function getMethod()
{
    if (null === $this->method) {
        $this->method = strtoupper($this->server->get('REQUEST_METHOD', 'GET'));

        if ('POST' === $this->method) {
            if ($method = $this->headers->get('X-HTTP-METHOD-OVERRIDE')) {
                $this->method = strtoupper($method);
            } elseif (self::$httpMethodParameterOverride) {
                $this->method = strtoupper($this->request->get('_method', $this->query->get('_method', 'POST')));
            }
        }
    }

    return $this->method;
}

The previous constant value should be true but shomehow, when I run the test its value is false.

回答1:

I found a solution but I don't think this is the right place to write it. I added a simple line of code on the Connector\Laravel5 class.

public function __construct($module)
{
    $this->module = $module;
    $this->initialize();

    $components = parse_url($this->app['config']->get('app.url', 'http://localhost'));
    $host = isset($components['host']) ? $components['host'] : 'localhost';

    parent::__construct($this->app, ['HTTP_HOST' => $host]);

    // Parent constructor defaults to not following redirects
    $this->followRedirects(true);

    // Added to solve the problem of overriding the request method
    Request::enableHttpMethodParameterOverride();
}

This solves my problem.



回答2:

You can not use PUT method in HTML form tag. For that you need to use laravel's blade template format to define form tag.

e.g. {!! Form::open(['url' => 'users/{users}','method' => 'put','id' => 'form' ]) !!}

Also you can use route attribute to define route instead of url.