CakePHP 3.4.2 Testing POST's response always r

2019-01-29 12:52发布

问题:

i'm currently testing an app that simply searches a record by the given id. It works fine but the testing refuses to return the response in the code. Strangely it is ONLY shown in the CLI.

I'm using phpunit provided by cakephp:

"phpunit/phpunit": "^5.7|^6.0"

Here is the conflicting code:

 $this->post('/comunas/findByBarrio',[
        'barrio_id'=>1
 ]);
 var_dump($this->_response->body());die(); //This is just a test which always returns NULL... while the CLI shows the actual response, which is a JSON.

Also the same problem occurrs while doing GET or POST to any other action. But here is the targeted controller's code:

public function findByBarrio()
{
    $this->autoRender = false;
    if ($this->request->is('POST'))
    {
        $data = $this->request->getData();
        if (!empty($data['barrio_id']))
        {
            $this->loadModel('Comuna');

            $barrio_id = $data['barrio_id'];

            $comuna = $this->Comuna->find('list',['conditions' => ['barrio_id'=>$barrio_id]])
                ->hydrate(false)
                ->toArray();
            if ($comuna)
            {
                echo json_encode($comuna);
            }
            else
            {
                throw new NotFoundException('0');
                //echo 0; //Comuna no encontrada para el barrio recibido
            }               
        }
        else
        {
            echo -1;
        }
    }
}

Thank you in advance!

UPDATE 1: I've only managed to get the output by using "ob_start()" and "ob_get_clean()" around the "$this->post" method. I wish there were a cleaner way though...

UPDATE 2: Now it's working! Just by using the PSR-7 compliant interface. Thank you! Here is the corrected controller:

public function findByBarrio()
{
    $this->autoRender = false;

    $this->response = $this->response->withType('json'); //CORRECTION

    if ($this->request->is('POST'))
    {
        $data = $this->request->getData();
        if (!empty($data['barrio_id']))
        {
            $this->loadModel('Comuna');

            $barrio_id = $data['barrio_id'];

            $comuna = $this->Comuna->find('list',['conditions' => ['barrio_id'=>$barrio_id]])
                ->hydrate(false)
                ->toArray();
            if ($comuna)
            {
                $json = json_encode($comuna);

                $this->response->getBody()->write($json); //CORRECTION

            }
            else
            {
                //Comuna no encontrada para el barrio recibido
                $this->response->getBody()->write(0); //CORRECTION
            }               
        }
        else
        {
            //No se recibió el barrio
            $this->response->getBody()->write(-1); //CORRECTION
        }
    }

    return $this->response; //CORRECTION
}

回答1:

Controller actions are not supposed to echo data, even though it might work in some, maybe even most situations. The correct way of outputting data that doesn't stem from a rendered view template, is to configure and return the response object, or to use serialized views.

The test environment relies on doing this properly, as it doesn't buffer possible output, but will use the actual value returned from the controller action.

The following is basically a copy from https://stackoverflow.com/a/42379581/1392379


Quote from the docs:

Controller actions generally use Controller::set() to create a context that View uses to render the view layer. Because of the conventions that CakePHP uses, you don’t need to create and render the view manually. Instead, once a controller action has completed, CakePHP will handle rendering and delivering the View.

If for some reason you’d like to skip the default behavior, you can return a Cake\Network\Response object from the action with the fully created response.

* As of 3.4 that would be \Cake\Http\Response

Cookbook > Controllers > Controller Actions

Configure the response

Using the PSR-7 compliant interface

$content = json_encode($comuna);

$this->response->getBody()->write($content);
$this->response = $this->response->withType('json');
// ...

return $this->response;

The PSR-7 compliant interface uses immutable methods, hence the utilization of the return value of withType(). Unlike setting headers and stuff, altering the body by writing to an existing stream doesn't change the state of the response object.

CakePHP 3.4.3 will add an immutable withStringBody method that can be used alternatively to writing to an existing stream.

$this->response = $this->response->withStringBody($content);

Using the deprecated interface

$content = json_encode($comuna);

$this->response->body($content);
$this->response->type('json');
// ...

return $this->response;

Use a serialized view

$content = json_encode($comuna);

$this->set('content', $content);
$this->set('_serialize', 'content');

This requires to also use the request handler component, and to enable extensing parsing and using correponsing URLs with .json appended, or to send a proper request with a application/json accept header.

See also

  • Cookbook > Controllers > Controller Actions
  • Cookbook > Views > JSON and XML views
  • PHP FIG Standards > PSR-7 HTTP message interfaces