Buy

Testing your API with PHPUnit

Testing your API with PHPUnit

What, a testing chapter so early? Yep, with API’s, you really can’t avoid it, and that’s a good thing. One way or another, to develop, test and debug each endpoint, you’ll need to either write some code or configure an HTTP browser plugin like Postman. And if you’re already doing all that work, you might as well make these calls repeatable and automated.

Guzzle and PHPUnit

Like I mentioned, if you’re making HTTP requests in PHP, you’ll want to use Guzzle, like we are in our testing.php file. So, the easiest way to create tests is just to put PHPUnit and Guzzle together.

I already have PHPUnit installed in our app via Composer, so let’s go straight to writing a test. Create a new Tests directory and put a ProgrammerControllerTest.php file there. Create a class inside and extend the normal PHPUnit base class:

// src/KnpU/CodeBattle/Tests/ProgrammerControllerTest.php
namespace KnpU\CodeBattle\Tests;

class ProgrammerControllerTest extends \PHPUnit_Framework_TestCase
{

}

Next, add a testPOST method and copy in the POST logic from the testing.php script:

// src/KnpU/CodeBattle/Tests/ProgrammerControllerTest.php
// ...

public function testPOST()
{
    // create our http client (Guzzle)
    $client = new Client('http://localhost:8000', array(
        'request.options' => array(
            'exceptions' => false,
        )
    ));

    $nickname = 'ObjectOrienter'.rand(0, 999);
    $data = array(
        'nickname' => $nickname,
        'avatarNumber' => 5,
        'tagLine' => 'a test dev!'
    );

    $request = $client->post('/api/programmers', null, json_encode($data));
    $response = $request->send();
}

Finally, let’s add some asserts to check that the status code is 201, that we have a Location header and that we get back valid JSON:

// src/KnpU/CodeBattle/Tests/ProgrammerControllerTest.php
// ...

public function testPOST()
{
    // ...

    $request = $client->post('/api/programmers', null, json_encode($data));
    $response = $request->send();

    $this->assertEquals(201, $response->getStatusCode());
    $this->assertTrue($response->hasHeader('Location'));
    $data = json_decode($response->getBody(true), true);
    $this->assertArrayHasKey('nickname', $data);
}

To try it out, use the vendor/bin/phpunit executable and point it at the test file.

$ php vendor/bin/phpunit src/KnpU/CodeBattle/Tests/ProgrammerControllerTest.php

With any luck, Sebastian Bergmann will tell you that everything is ok! Of course I never trust a test that passes on the first try, so be sure to change things and make sure it fails when it should too.

Leave a comment!

  • 2016-08-17 Chmlls

    This one worked for me, thanks Rakib!

  • 2016-08-17 weaverryan

    Yay! That's awesome! And this is a great solution - running vendor/bin/phpunit is simply a shortcut to run the command that you ran - so it is actually no different at all.

    Good luck and let us know if you have any other issues!

    Cheers!

  • 2016-08-17 叶朱丽

    Hi Victor!
    I was eventually able to run the test without strange errors by using this command:
    php vendor/phpunit/phpunit/composer/bin/phpunit
    I cannot take credit for this, though... it was suggested few hours ago in a post below in this page, as you can see.
    Anyway, thank you so much for your help and time! :D
    Giulietta

  • 2016-08-17 Victor Bocharsky

    Hey Rakib,

    In my project with PHPUnit 4.8 the direct path to the phpunit executable file is: ./vendor/phpunit/phpunit/phpunit . So probably it depends on PHPUnit version. But I think you're right, possible solution is to find manually the phpunit executable file in "./vendor/phpunit/phpunit" or nested folders and try to run it directly.

    Cheers!

  • 2016-08-17 Rakib Ahmed Shovon

    try this one . php vendor/phpunit/phpunit/composer/bin/phpunit

  • 2016-08-15 weaverryan

    Hi again!

    Ah, that's partially my mistake. Run this instead:

    ./vendor/bin/phpunit

    or just

    vendor/bin/phpunit

    (I can't remember if both work on Windows, or just one - but they are really the same thing)

    I should not have recommended "php vendor/bin/phpunit" - as it doesn't work on Windows (the command above will work on everything). But, this is an interesting topic on its own. When you run "phpunit", it uses your global, system-wide installation of phpunit (if you have one). But if you run ./vendor/bin/phpunit. it uses the phpunit that was installed into your project via Composer. Sometimes, both will work, but the in-project one is better :).

    In fact, I think this is the problem! If you use the "project" phpunit (i.e. vendor/bin/phpunit), it is aware of the other vendor/ files in your project, and will make them available. I think this is why you receive the "Class not found" error: when you run the "global" phpunit, it doesn't auto-load the classes in your project's vendor directory.

    So, try the above command and let me know if it works. Your test code (obviously, since you haven't changed it) and your composer.json both look very good to me :).

    I think we're close! Cheers!

  • 2016-08-13 叶朱丽

    Hi Ryan!
    I go exactly through the same steps you showed me, but the only difference is that I have to run the test command as 'phpunit' only, instead of if 'php vendor/bin/phpunit ', because it gives me this message:

    dir=$(d=${0%[/\\]*}; cd "$d"; cd "../phpunit/phpunit/composer/bin" && pwd)

    # See if we are running in Cygwin by checking for cygpath program
    if command -v 'cygpath' >/dev/null 2>&1; then
    # Cygwin paths start with /cygdrive/ which will break windows PHP,
    # so we need to translate the dir path to windows format. However
    # we could be using cygwin PHP which does not require this, so we
    # test if the path to PHP starts with /cygdrive/ rather than /usr/bin
    if [[ $(which php) == /cygdrive/* ]]; then
    dir=$(cygpath -m "$dir");
    fi
    fi
    dir=$(echo $dir | sed 's/ /\ /g')
    "${dir}/phpunit" "$@"

    So, after running 'phpunit src/KnpU/CodeBattle/Tests/ProgrammerControllerTest.php', I always get this message:

    Fatal error: Uncaught Error: Class 'Guzzle\Http\Client' not found in C:\finish\src\KnpU\CodeBattle\Tests\ProgrammerControllerTest.php:11
    Stack trace:
    #0 [internal function]: KnpU\CodeBattle\Tests\ProgrammerControllerTest->testPOST()
    #1 C:\xampp\php\pear\PHPUnit\Framework\TestCase.php(975): ReflectionMethod->invokeArgs(Object(KnpU\CodeBattle\Tests\ProgrammerControllerTest), Array)
    #2 C:\xampp\php\pear\PHPUnit\Framework\TestCase.php(831): PHPUnit_Framework_TestCase->runTest()
    #3 C:\xampp\php\pear\PHPUnit\Framework\TestResult.php(648): PHPUnit_Framework_TestCase->runBare()
    #4 C:\xampp\php\pear\PHPUnit\Framework\TestCase.php(776): PHPUnit_Framework_TestResult->run(Object(KnpU\CodeBattle\Tests\ProgrammerControllerTest))
    #5 C:\xampp\php\pear\PHPUnit\Framework\TestSuite.php(775): PHPUnit_Framework_TestCase->run(Object(PHPUnit_Framework_TestResult))
    #6 C:\xampp\php\pear\PHPUnit\Framework\TestSuite.php(745): PHPUnit_Framework_TestSuite->runTest(Object(KnpU\CodeBattle\Tests\ProgrammerControllerTest), Object(PHPUnit_Framework_TestRe in C:\finish\src\KnpU\CodeBattle\Tests\ProgrammerControllerTest.php on line 11
    PHP Fatal error: Uncaught Error: Class 'Guzzle\Http\Client' not found in C:\finish\src\KnpU\CodeBattle\Tests\ProgrammerControllerTest.php:11
    Stack trace:
    #0 [internal function]: KnpU\CodeBattle\Tests\ProgrammerControllerTest->testPOST()
    #1 C:\xampp\php\pear\PHPUnit\Framework\TestCase.php(975): ReflectionMethod->invokeArgs(Object(KnpU\CodeBattle\Tests\ProgrammerControllerTest), Array)
    #2 C:\xampp\php\pear\PHPUnit\Framework\TestCase.php(831): PHPUnit_Framework_TestCase->runTest()
    #3 C:\xampp\php\pear\PHPUnit\Framework\TestResult.php(648): PHPUnit_Framework_TestCase->runBare()
    #4 C:\xampp\php\pear\PHPUnit\Framework\TestCase.php(776): PHPUnit_Framework_TestResult->run(Object(KnpU\CodeBattle\Tests\ProgrammerControllerTest))
    #5 C:\xampp\php\pear\PHPUnit\Framework\TestSuite.php(775): PHPUnit_Framework_TestCase->run(Object(PHPUnit_Framework_TestResult))
    #6 C:\xampp\php\pear\PHPUnit\Framework\TestSuite.php(745): PHPUnit_Framework_TestSuite->runTest(Object(KnpU\Code

    This is my composer.json:

    {
    "require": {
    "silex/silex": "~1.0",
    "symfony/twig-bridge": "~2.1",
    "symfony/security": "~2.4",
    "doctrine/dbal": "^2.5.4",
    "monolog/monolog": "~1.7.0",
    "symfony/validator": "~2.4",
    "symfony/expression-language": "~2.4"
    },
    "require-dev": {
    "behat/mink": "~1.5",
    "behat/mink-goutte-driver": "~1.0.9",
    "behat/mink-selenium2-driver": "~1.1.1",
    "behat/behat": "~2.5",
    "behat/mink-extension": "~1.2.0",
    "phpunit/phpunit": "~3.7.0",
    "guzzle/guzzle": "~3.7"
    },
    "autoload" : {
    "psr-0": {
    "KnpU\\CodeBattle": "src/"
    } }}

    I haven't touched the test file, anyway, pasting it here too:

    array(
    'exceptions' => false,
    )
    ));

    $nickname = 'ObjectOrienter'.rand(0, 999);
    $data = array(
    'nickname' => $nickname,
    'avatarNumber' => 5,
    'tagLine' => 'a test dev!'
    );

    $request = $client->post('/api/programmers', null, json_encode($data));
    $response = $request->send();

    $this->assertEquals(201, $response->getStatusCode());
    $this->assertTrue($response->hasHeader('Location'));
    $data = json_decode($response->getBody(true), true);
    $this->assertArrayHasKey('nickname', $data);
    }
    }

    If I won't be able to solve this problem I will just try going on with the following lesson ... But, in this case, would you suggest another way to test the Api?

    Thank you very much for your time :)

    Giulietta

  • 2016-08-12 weaverryan

    No problem :) - sorry you're still having issues!

    Ok, so first, I don't *think* that the PHPUnit\Autoload.php file is the problem. When you run composer install, it downloads all of the files into the vendor directory - and it should do this perfectly (I mean, it downloads all of phpunit, and there shouldn't be any path problems). In fact, I just re-downloaded the code to make sure that everything is still ok with it, and I *was* able to get the phpunit test to pass. Here's what I did:

    1) Downloaded and unzipped the course code
    2) Moved into the finish directory and ran "composer install"
    3) Moved into the web directory and ran php -S localhost:8000 to start the built-in web server
    4) Execute the tests by running:

    php vendor/bin/phpunit src/KnpU/CodeBattle/Tests/ProgrammerControllerTest.php

    When I did this, the one test passed just fine :). Now, in your last comment, you said that you "keep on getting the same error". Which error is this? Is it the "Class Guzzle\Http\Client not found" error? Or is it something different? Can you re-paste your composer.json (unless you haven't changed it) and your test file? I'm sure it's something small :).

    Cheers!

  • 2016-08-12 叶朱丽

    Hi Ryan,

    thank you for your detailed explanation and sorry to bother you again with this issue, but I am kind of stuck.

    I went back to the original guzzle/guzzle library but keep on getting the same error. After a long (but fruitless) search on the web I thought I could try to run the test on the 'finish' version of the API provided in the course code (perhaps I messed up too much with the other one).
    So I moved the folder to the server and ran composer install command.
    Unfortunately I still get the very same error when running the PhpUnit test. I didn't modify the composer.json file.
    Then I tried to dig a bit deeper but only noticed several yellow warnings (path not found) on the file vendor\phpunit\phpunit\PHPUnit\Autoload.php.
    All the 'not found' files are those in the 'require_once' statements (see below); do you think this could be related to the error I am getting?
    Thanks a lot for your help!!
    Giulietta

    require_once $path . '/autoload.php';
    (....)
    require_once 'File/Iterator/Autoload.php';
    require_once 'PHP/CodeCoverage/Autoload.php';
    require_once 'PHP/Timer/Autoload.php';
    require_once 'PHPUnit/Framework/MockObject/Autoload.php';
    require_once 'Text/Template/Autoload.php';
    (....)
    if (stream_resolve_include_path('PHP/Invoker/Autoload.php')) {
    require_once 'PHP/Invoker/Autoload.php';
    }

    if (stream_resolve_include_path('PHPUnit/Extensions/Database/Autoload.php')) {
    require_once 'PHPUnit/Extensions/Database/Autoload.php';
    }

    if (stream_resolve_include_path('PHPUnit/Extensions/SeleniumCommon/Autoload.php')) {
    require_once 'PHPUnit/Extensions/SeleniumCommon/Autoload.php';
    }

    else if (stream_resolve_include_path('PHPUnit/Extensions/SeleniumTestCase/Autoload.php')) {
    require_once 'PHPUnit/Extensions/SeleniumTestCase/Autoload.php';
    }

    if (stream_resolve_include_path('PHPUnit/Extensions/Story/Autoload.php')) {
    require_once 'PHPUnit/Extensions/Story/Autoload.php';

  • 2016-08-11 weaverryan

    Hey Giulietta!

    Ok, I think we're close - there is one confusing thing going on! Here's some history:

    A) The package called guzzle/guzzle is used in this tutorial. But this package has been deprecated and replaced by guzzlehttp/guzzle. guzzle/guzzle still works and is available, so you can use it still (and follow along with the tutorial exactly) or you can use guzzlehttp/guzzle, but then you'll need to update some of your code for changes in this new library.

    B) In your composer.json, you are using the new library - guzzlehttp/guzzle.

    C) Based on which library you have, there are some changes you need to make to your code:

    - if you're using guzzle/guzzle, then the Client's namespace is Guzzle\Http\Client
    - if you're using guzzlehttp/guzzle, then the Client's namespace is GuzzleHttp\Client

    So, it looks to me like you're using the new package (guzzlehttp/guzzle) but you are using the *old* library's namespace (Guzzle\Http\Client). You have two options:

    A) Replace guzzlehttp/guzzle with guzzle/guzzle (the old library) and code along exactly with the tutorial. This is great if you want to focus on learning the REST stuff, and don't want to worry about the little details of using Guzzle, which we use to test the API

    B) Keep guzzlehttp/guzzle, and then do a little more work to update / translate the code we use in this tutorial into the updated code. This is good if you're really curious about learning Guzzle and don't mind doing some extra digging for some extra learning! If you go this direction and have any issues you can't solve, we'll be happy to help with those :).

    Let me know if this helps! And cheers!

  • 2016-08-10 叶朱丽

    Hi Victor!
    I had already tried changing the use statement, but it didn't work (the namespace of the Client class is actually Guzzle\Http). Also ran composer install (it says that --dev is deprecated) and composer update. It gives me this message where advises not to use guzzle/guzzle (not too sure what I should do though):

    C:\xampp\htdocs\restone>composer update
    Loading composer repositories with package information
    Updating dependencies (including require-dev)
    - Removing guzzlehttp/streams (2.1.0)
    Package guzzle/guzzle is abandoned, you should avoid using it. Use guzzlehttp/guzzle instead.
    Writing lock file
    Generating autoload files

    I am sorry cannot find the solution. Am I missing something?
    Could you please help me checking if my composer.json is alright?
    I've tried googling the error but none of the suggestions I found seems to help :(
    Thank you!!
    Giulietta

    {
    "require": {
    "silex/silex": "~1.0",
    "symfony/twig-bridge": "~2.1",
    "symfony/security": "~2.4",
    "doctrine/dbal": "^2.5.4",
    "monolog/monolog": "~1.7.0",
    "symfony/validator": "~2.4",
    "symfony/expression-language": "~2.4",
    "guzzlehttp/guzzle": "^6.2"

    },
    "require-dev": {
    "behat/mink": "~1.5",
    "behat/mink-goutte-driver": "~1.0.9",
    "behat/mink-selenium2-driver": "~1.1.1",
    "behat/behat": "~2.5",
    "behat/mink-extension": "~1.2.0",
    "phpunit/phpunit": "~3.7.0"
    },
    "autoload" : {
    "psr-0": {
    "KnpU\\CodeBattle": "src/"
    }
    }
    }

  • 2016-08-10 Victor Bocharsky

    Hey Giulietta,

    You probably should use a "\GuzzleHttp\Client" instead of "\Guzzle\Http\Client" in "ProgrammerControllerTest". Or probably you missed a package or maybe do not install dev packages with Composer. Try to re-run "composer install --dev" command. It should help. If not - check what Guzzle version you have installed. if it even not installed yet - install it first with "composer require guzzlehttp/guzzle" command and re-run your tests.

    Cheers!

  • 2016-08-09 叶朱丽

    Hi!
    When I try running the phpunit test I get the following error on the command prompt, any suggestions?
    Thank you :)
    Giulietta

    Fatal error: Uncaught Error: Class 'Guzzle\Http\Client' not found in C:\xampp\htdocs\restone\src\KnpU\CodeBattle\Tests\Controller\Api\ProgrammerControllerTest.php:11

    Stack trace:
    #0 [internal function]: KnpU\CodeBattle\Tests\Controller\Api\ProgrammerControllerTest->testPOST()
    #1 C:\xampp\php\pear\PHPUnit\Framework\TestCase.php(975): ReflectionMethod->invokeArgs(Object(KnpU\CodeBattle\Tests\Controller\Api\ProgrammerControllerTest), Array)
    #2 C:\xampp\php\pear\PHPUnit\Framework\TestCase.php(831): PHPUnit_Framework_TestCase->runTest()
    #3 C:\xampp\php\pear\PHPUnit\Framework\TestResult.php(648): PHPUnit_Framework_TestCase->runBare()
    #4 C:\xampp\php\pear\PHPUnit\Framework\TestCase.php(776): PHPUnit_Framework_TestResult->run(Object(KnpU\CodeBattle\Tests\Controller\Api\ProgrammerControllerTest))
    #5 C:\xampp\php\pear\PHPUnit\Framework\TestSuite.php(775): PHPUnit_Framework_TestCase->run(Object(PHPUnit_Framework_TestResult))
    #6 C:\xampp\php\pear\PHPUnit\Framework\TestSuite.php(745): PHPUnit_Framework_TestSuite->runTest(Object(KnpU\C in C:\xampp\htdocs\restone\src\KnpU\CodeBattle\Tests\Controller\Api\ProgrammerControllerTest.php on line 11
    PHP Fatal error: Uncaught Error: Class 'Guzzle\Http\Client' not found in C:\xampp\htdocs\restone\src\KnpU\CodeBattle\Tests\Controller\Api\ProgrammerControllerTest.php:11
    Stack trace:
    #0 [internal function]: KnpU\CodeBattle\Tests\Controller\Api\ProgrammerControllerTest->testPOST()
    #1 C:\xampp\php\pear\PHPUnit\Framework\TestCase.php(975): ReflectionMethod->invokeArgs(Object(KnpU\CodeBattle\Tests\Controller\Api\ProgrammerControllerTest), Array)
    #2 C:\xampp\php\pear\PHPUnit\Framework\TestCase.php(831): PHPUnit_Framework_TestCase->runTest()
    #3 C:\xampp\php\pear\PHPUnit\Framework\TestResult.php(648): PHPUnit_Framework_TestCase->runBare()
    #4 C:\xampp\php\pear\PHPUnit\Framework\TestCase.php(776): PHPUnit_Framework_TestResult->run(Object(KnpU\CodeBattle\Tests\Controller\Api\ProgrammerControllerTest))
    #5 C:\xampp\php\pear\PHPUnit\Framework\TestSuite.php(775): PHPUnit_Framework_TestCase->run(Object(PHPUnit_Framework_TestResult))
    #6 C:\xampp\php\pear\PHPUnit\Framework\TestSuite.php(745): PHPUnit_Framework_TestSuite->runTest(Object(KnpU\C in C:\xampp\htdocs\restone\src\KnpU\CodeBattle\Tests\Controller\Api\ProgrammerControllerTest.php on line 11

  • 2015-09-09 Samuel

    nevermind^^ seems i was too slow to catch it :)

  • 2015-09-09 Samuel

    Are you sure you want to do the assertArrayHasKey on the $data used for the request and not what came back on the response?

  • 2015-03-26 Dizzy Bryan High

    Hi there am using php storm on windows to do this tutorial, i have php 5.4.* installed.
    I cant seem to run the phpUnit tests.
    It just seems to echo out whats in the vendor\bin\phpunit file
    I've tried downloading the pupunit.phar and putting it into the vendor\bin\ dir an running
    php vendor/bin/phpunit.phar src/KnpU/CodeBattle/Tests/ProgrammerControllerTest.php
    Just returns nothing.

    Kinda stuck here, not sure how to move forward