Testing laravel controllers with JSON request body
Asked Answered
D

7

12

I am trying to write a phpunit test for a Laravel controller which expects post requests with a body in JSON format.

A simplified version of the controller:

class Account_Controller extends Base_Controller
{
    public $restful = true;

    public function post_login()
    {
        $credentials = Input::json();
        return json_encode(array(
            'email' => $credentials->email,
            'session' => 'random_session_key'
        ));
    }
}

Currently I have a test method which is correctly sending the data as urlencoded form data, but I cannot work out how to send the data as JSON.

My test method (I used the github gist here when writing the test)

class AccountControllerTest extends PHPUnit_Framework_TestCase {
    public function testLogin()
    {
        $post_data = array(
            'email' => '[email protected]',
            'password' => 'example_password'
        );
        Request::foundation()->server->set('REQUEST_METHOD', 'POST');
        Request::foundation()->request->add($post_data);
        $response = Controller::call('account@login', $post_data);
        //check the $response
    }
}

I am using angularjs on the frontend and by default, requests sent to the server are in JSON format. I would prefer not to change this to send a urlencoded form.

Does anyone know how I could write a test method which provides the controller with a JSON encoded body?

Durban answered 5/1, 2013 at 12:31 Comment(0)
A
2

There is a lot easier way of doing this. You can simply set Input::$json property to the object you want to send as post parameter. See Sample code below

 $data = array(
        'name' => 'sample name',
        'email' => '[email protected]',
 );

 Input::$json = (object)$data;

 Request::setMethod('POST');
 $response = Controller::call('client@create');
 $this->assertNotNull($response);
 $this->assertEquals(200, $response->status());

I hope this helps you with your test cases

Update : The original article is available here http://forums.laravel.io/viewtopic.php?id=2521

Avion answered 19/1, 2013 at 1:47 Comment(3)
I had a chance to rewrite some of my tests using this method. I found it more useful as it allows me to check if a single piece of code was working instead of all the code used for a http request. When something does break, this method also provides a traceback which is more useful while debugging than a http status code.Durban
When I try this, I get Fatal error: Access to undeclared static property: Illuminate\Support\Facades\Input::$json - am I missing some context?Oliveolivegreen
@AaronPollock because json() is a method, not a property. Use it as Input::json()Peag
S
10

In Laravel 5, the call() method has changed:

$this->call(
    'PUT', 
    $url, 
    [], 
    [], 
    [], 
    ['CONTENT_TYPE' => 'application/json'],
    json_encode($data_array)
);

I think that Symphony's request() method is being called: http://symfony.com/doc/current/book/testing.html

Steven answered 19/3, 2015 at 12:12 Comment(0)
M
6

This is how I go about doing this in Laravel4

// Now Up-vote something with id 53
$this->client->request('POST', '/api/1.0/something/53/rating', array('rating' => 1) );

// I hope we always get a 200 OK
$this->assertTrue($this->client->getResponse()->isOk());

// Get the response and decode it
$jsonResponse = $this->client->getResponse()->getContent();
$responseData = json_decode($jsonResponse);

$responseData will be a PHP object equal to the json response and will allow you to then test the response :)

Mcneil answered 10/7, 2013 at 11:13 Comment(0)
O
5

Here's what worked for me.

$postData = array('foo' => 'bar');
$postRequest = $this->action('POST', 'MyController@myaction', array(), array(), array(), array(), json_encode($postData));
$this->assertTrue($this->client->getResponse()->isOk());

That seventh argument to $this->action is content. See docs at http://laravel.com/api/source-class-Illuminate.Foundation.Testing.TestCase.html#_action

Oliveolivegreen answered 12/3, 2014 at 16:10 Comment(2)
I was also searching for the solution and find the same answer in the docs.Histology
Good answer, but make sure to pass the json to the 6th parameter, not the 7th.Everard
A
2

There is a lot easier way of doing this. You can simply set Input::$json property to the object you want to send as post parameter. See Sample code below

 $data = array(
        'name' => 'sample name',
        'email' => '[email protected]',
 );

 Input::$json = (object)$data;

 Request::setMethod('POST');
 $response = Controller::call('client@create');
 $this->assertNotNull($response);
 $this->assertEquals(200, $response->status());

I hope this helps you with your test cases

Update : The original article is available here http://forums.laravel.io/viewtopic.php?id=2521

Avion answered 19/1, 2013 at 1:47 Comment(3)
I had a chance to rewrite some of my tests using this method. I found it more useful as it allows me to check if a single piece of code was working instead of all the code used for a http request. When something does break, this method also provides a traceback which is more useful while debugging than a http status code.Durban
When I try this, I get Fatal error: Access to undeclared static property: Illuminate\Support\Facades\Input::$json - am I missing some context?Oliveolivegreen
@AaronPollock because json() is a method, not a property. Use it as Input::json()Peag
D
1

A simple solution would be to use CURL - which will then also allow you to capture the 'response' from the server.

class AccountControllerTest extends PHPUnit_Framework_TestCase
{

 public function testLogin()
 {
    $url = "account/login";

    $post_data = array(
        'email' => '[email protected]',
        'password' => 'example_password'
    );
    $content = json_encode($post_data);

    $curl = curl_init($url);
    curl_setopt($curl, CURLOPT_HEADER, false);
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($curl, CURLOPT_HTTPHEADER, array("Content-type: application/json"));
    curl_setopt($curl, CURLOPT_POST, true);
    curl_setopt($curl, CURLOPT_POSTFIELDS, $content);

    $json_response = curl_exec($curl);

    $status = curl_getinfo($curl, CURLINFO_HTTP_CODE);

    curl_close($curl);

    $response = json_decode($json_response, true);

    // Do some $this->Assert() stuff here on the $status
  }
}

CURL will actually simulate the raw HTTP post with JSON - so you know you are truly testing your functionality;

Daggna answered 5/1, 2013 at 12:45 Comment(2)
Good approach. But I'd recommend to use Httpful bundle to keep the test code cleaner.Cocoa
I ended up creating a class which wraps all the curl functionality into a few easier to use functions and updating my tests to inherit from the new class. ApiTestCase.phpDurban
B
1

As of Laravel 5.1 there is a much easier way to test JSON controllers via PHPunit. Simply pass an array with the data and it'll get encoded automatically.

public function testBasicExample()
{
    $this->post('/user', ['name' => 'Sally'])
         ->seeJson([
            'created' => true,
         ]);
}

From the docs: http://laravel.com/docs/5.1/testing#testing-json-apis

Bracy answered 9/6, 2015 at 17:49 Comment(2)
I don't think this makes the request itself have a content-type header of application/json. From what I can tell, it will still send application/x-www-form-urlencoded to the /users endpoint. I believe the way to force the request out with json is to do what @Steven does.Messier
Use $this->json('POST', ... instead of $this->post(... to use the appropriate content-types.Washin
I
0

Since at least Laravel 5.2 there is a json() method in Illuminate\Foundation\Testing\Concerns\MakesHttpRequests therefore you can do the following:

$data = [
  "name" => "Foobar"
];
$response = $this->json('POST', '/endpoint', $data);

Also since Laravel 5.3 there are also convenient methods like putJson(), postJson(), etc. Therefore it can be even shortened further to:

$data = [
  "name" => "Foobar"
];
$response = $this->postJson('/endpooint', $data);

And then you can do $response->assertJson(...) like:

$response->assertJson(fn (AssertableJson $json) => $json->hasAll(['id', 'name']));
Inelegant answered 22/3, 2021 at 19:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.