3

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.

Lerzenit
  • 449
  • 4
  • 12
  • Run your test with -vv flag to see what parameters are actually sent. – Naktibalda Jan 21 '16 at 12:37
  • Done. The output shows how Codeception is doing a `POST` request with a `_method = PUT` attribute as expected. But somehow this does not match with my PUT route defined. However, doing this in a web browser matches correctly. – Lerzenit Jan 21 '16 at 15:52
  • I have the same problem on the update treatment. I systematically have a 404 error. In fact, the update method is not executed in my controller, which lead codeception on the update url without the redirection that should happen after the treatment in the update method. I block on this problem since two weeks now without finding any solution or clue to find one. – Okipa Jan 21 '16 at 17:04
  • Please stop writing that Codeception is not reading the _method value, you already confirmed that _method is passed in request. – Naktibalda Jan 21 '16 at 22:18
  • Do you use PhpBrowser or Laravel5 module? – Naktibalda Jan 21 '16 at 22:21
  • I use the Laravel5 module. I also use the Mailcatcher module but it seems to have no impact on this problem. – Okipa Jan 22 '16 at 09:54
  • By the way I also confirm that the params are correctly passed in the http request (including the _method value). I suspect that Codeception doesn't "understand" that the update method from the controller is called. I think that if we use a custom POST request it will work, this is probably why Lerzenit explained that the problem is probably linked to the _method value which is misunderstood by Codeception.. – Okipa Jan 22 '16 at 10:03
  • 1
    It is down to Laravel to interpret parameters and call the right method. Codeception test suite includes Laravel5 site, which tests form similar to yours with method set to PATCH and it passes: https://github.com/janhenkgerritsen/codeception-laravel5-sample/blob/codeception-2.1/resources/views/posts/edit.blade.php – Naktibalda Jan 22 '16 at 10:26
  • I use both Laravel5 and PhpBrowser. My question is... why Laravel interprets correctly the request when I on my web browser but is unable to do the same on a codeception test?. Maybe the problem is on PhpBrowser or Laravel5 module. – Lerzenit Jan 23 '16 at 10:04
  • Using PhpBrowser and Laravel5 module in the same suite is wrong and it is a potential cause for issues. Pick one. – Naktibalda Jan 23 '16 at 20:40
  • Thank you @Naktibalda for ypur advice. Unfortunately, I removed PhpBrowser and the error persists. – Lerzenit Jan 25 '16 at 08:21

2 Answers2

2

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.

Lerzenit
  • 449
  • 4
  • 12
1

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.

Ali
  • 1,408
  • 10
  • 17
  • I already do that. Laravel Form helper class transforms that code into the HTML code above. The problem is Codeception not reading the _method value. – Lerzenit Jan 21 '16 at 15:43