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?

7 Answers 7

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

Sign up to request clarification or add additional context in comments.

Comments

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 :)

Comments

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

2 Comments

I was also searching for the solution and find the same answer in the docs.
Good answer, but make sure to pass the json to the 6th parameter, not the 7th.
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

3 Comments

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.
When I try this, I get Fatal error: Access to undeclared static property: Illuminate\Support\Facades\Input::$json - am I missing some context?
@AaronPollock because json() is a method, not a property. Use it as Input::json()
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;

2 Comments

Good approach. But I'd recommend to use Httpful bundle to keep the test code cleaner.
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.php
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

2 Comments

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 @eoinoc does.
Use $this->json('POST', ... instead of $this->post(... to use the appropriate content-types.
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']));

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.