Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
This tutorial has a new version, check it out!

Test Code Reuse

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $10.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

We'll have a bunch of test classes and they'll all need to create a Guzzle Client with these options. So let's just get organized now.

Create a new Test directory in the bundle and a new class called ApiTestCase. This will be a base class for all our API tests. Make it extend the normal PHPUnit_Framework_TestCase:

... lines 1 - 2
namespace AppBundle\Test;
... lines 4 - 6
class ApiTestCase extends \PHPUnit_Framework_TestCase
... lines 9 - 29

Right now, the thing I want to move out of each test class is the creation of the Guzzle Client. So copy that code. In ApiTestCase, override a method called setupBeforeClass() - it's static. PHPUnit calls this one time at the beginning of running your whole test suite.

Paste the $client code here. Because really, even if we run A LOT of tests, we can probably always use the same Guzzle client. Create a private static property called $staticClient and put the Client there with self::$staticClient. And give Client a proper use statement:

... lines 1 - 6
class ApiTestCase extends \PHPUnit_Framework_TestCase
private static $staticClient;
... lines 10 - 15
public static function setUpBeforeClass()
self::$staticClient = new Client([
'base_url' => 'http://localhost:8000',
'defaults' => [
'exceptions' => false
... lines 25 - 29


In case you are using Guzzle 6, you would need to use the base_uri key instead of base_url to configure Guzzle client properly.

Cool. So now the Client is created once per test suite. Now, create a protected $client property that is not static with some nice PHPDoc above it. Woops - make sure you actually make this protected: this is what we'll use in the sub-classes. Then, override setup() and say $this->client = self::$staticClient:

... lines 1 - 6
class ApiTestCase extends \PHPUnit_Framework_TestCase
... lines 9 - 10
* @var Client
protected $client;
... lines 15 - 25
protected function setUp()
$this->client = self::$staticClient;

setupBeforeClass() will make sure the Client is created just once and setup() puts that onto a non-static property, just because I like non-static things a bit better. Oh, and if we did need to do any clean up resetting of the Client, we could do that in setup() or tearDown().

Extend the Base Class

Back in the actual test class, get rid of the $client code and simply reference $this->client. Ooooo, and don't forget to extend ApiTestCase like I just did:

... lines 1 - 3
use AppBundle\Test\ApiTestCase;
class ProgrammerControllerTest extends ApiTestCase
public function testPOST()
... lines 10 - 16
// 1) Create a programmer resource
$response = $this->client->post('/api/programmers', [
'body' => json_encode($data)
... lines 21 - 25

Make sure we didn't break anything:

php bin/phpunit -c app src/AppBundle/Tests/Controller/Api/ProgrammerControllerTest.php

Hey, still green!

Leave a comment!

Login or Register to join the conversation

APITestCase for Symfony4 seems to break. I'm getting user not found. It seems to be still hitting my app database instead of app_test database.


Hey azeem

Probably you forgot to override the database url parameter in your "phpunit.xml.dist" file
You can find more info here: https://symfony.com/doc/cur...



I have that set already. And, it hits the correct `app_test` schema if I make any changes via entity manager. However, I am testing an api endpoint for e.g. `/app_test.php/tokens` via GuzzleHttp. if I do `var_dump(getenv('APP_ENV'), getenv('database_url'))` I get 'dev' and `mysql://username:secret@localhost:3306/apps`


Hey azeem!

Hmmm. So basically, when you interact with the database inside your test (e.g. add/delete rows), this correctly happens against the test database. But when you actually make the Guzzle request to /app_test.php/tokens, this hits the "dev" environment. Is that correct?

In that case, what does your app_test.php file look like? And also, in what file are you overriding the database credentials for the test environment?

Indeed, the way that you override some of these variables is a bit different in Symfony 4 :).


Cat in space

"Houston: no signs of life"
Start the conversation!

This tutorial uses an older version of Symfony. The concepts of REST are still valid, but I recommend using API Platform in new Symfony apps.

What PHP libraries does this tutorial use?

// composer.json
    "require": {
        "php": ">=5.3.3",
        "symfony/symfony": "2.6.*", // v2.6.11
        "doctrine/orm": "~2.2,>=2.2.3,<2.5", // v2.4.7
        "doctrine/dbal": "<2.5", // v2.4.4
        "doctrine/doctrine-bundle": "~1.2", // v1.4.0
        "twig/extensions": "~1.0", // v1.2.0
        "symfony/assetic-bundle": "~2.3", // v2.6.1
        "symfony/swiftmailer-bundle": "~2.3", // v2.3.8
        "symfony/monolog-bundle": "~2.4", // v2.7.1
        "sensio/distribution-bundle": "~3.0,>=3.0.12", // v3.0.21
        "sensio/framework-extra-bundle": "~3.0,>=3.0.2", // v3.0.7
        "incenteev/composer-parameter-handler": "~2.0", // v2.1.0
        "hautelook/alice-bundle": "0.2.*", // 0.2
        "jms/serializer-bundle": "0.13.*" // 0.13.0
    "require-dev": {
        "sensio/generator-bundle": "~2.3", // v2.5.3
        "behat/behat": "~3.0", // v3.0.15
        "behat/mink-extension": "~2.0.1", // v2.0.1
        "behat/mink-goutte-driver": "~1.1.0", // v1.1.0
        "behat/mink-selenium2-driver": "~1.2.0", // v1.2.0
        "phpunit/phpunit": "~4.6.0" // 4.6.4