PHPUnit: Secure the Park

Video not working?

It looks like your browser may not support the H264 codec. If you're using Linux, try a different browser or try installing the gstreamer0.10-ffmpeg gstreamer0.10-plugins-good packages.

Thanks! This saves us from needing to use Flash or encode videos in multiple formats. And that let's us get back to making more videos :). But as always, please feel free to message us.

Let me paint you a scary picture:

It's Friday night. It's stormy. The office is empty and you're deploying fresh code straight to production. Suddenly, an alarm! What? The fences are down!? The dinosaurs are escaping!? Somehow, your beautiful code contained a bug. And as the raptors surround you, one thought keeps coming back: if only you had written tests.

I hate bugs, I hate fixing emergencies on production, and I especially hate being eaten by raptors.

It's time to go pro with coding... and that means, learning to test! And not just because we hate running from dinosaurs. We want to write code that is thoughtfully-designed and have the ability to add new features with confidence.

Hello Dinosaur Park

To make this dream come alive, you should totally code along with me. Download the course code from this page. After unzipping the file and turning the fences back on, you should find a start/ directory that has the same code you see here. Open up the README.md file for setup instructions and directions on how to catch the last boat off the island before the storm.

The last step is to find a terminal, move into your project, and run:

./bin/console server:run

to start the built-in PHP web server. In your browser, go to http://localhost:8000 to find... well... nothing! Just, "Welcome to Dinosaur Park". Instead of creating a park full of dinosaurs and then worrying about security... ahem... we don't have any code yet. We're going to build this dino factory and write tests all at the same time.

Installing PHPUnit

The de facto standard tool for testing in PHP is PHPUnit. Open a new terminal tab. Install it with:

composer require --dev phpunit/phpunit

This will obviously download the PHPUnit library into your vendor/ directory. But mostly, you will interact with PHPUnit as an executable. When this finishes, you can now run:

./vendor/bin/phpunit

Hi PHPUnit! And hello Sebastian Bergmann and other contributors! There are no tests yet but I already feel like we're making friends.

Write some Tests

Let's write our first test. But, uh, don't worry about what we're testing yet - let's just experiment a little.

Create a tests/ directory and inside, another AppBundle/Entity directory. We'll talk about this structure soon, but first we have dinosaurs to contain!

Add a new PHP class: DinosaurTest, and give it a namespace: Tests\AppBundle\Entity. Make sure you extend a class: TestCase from PHPUnit.

... lines 1 - 2
namespace Tests\AppBundle\Entity;
... line 4
use PHPUnit\Framework\TestCase;
... line 6
class DinosaurTest extends TestCase
{
... lines 9 - 12
}

To actually make a test, create a public function called testThatYourComputerWorks. We're giving it that name because, inside, we're going to say $this->assertTrue(false).

... lines 1 - 6
class DinosaurTest extends TestCase
{
public function testThatYourComputerWorks()
{
$this->assertTrue(false);
}
}

If this test passes, you'll know to throw your computer out of the window and buy a new one. Let's find out. To run the tests, find your terminal, and re-run PHPUnit:

./vendor/bin/phpunit

Yes! It fails! My computer gets to live! It failed asserting that false is true on DinosaurTest line 11.

Test Rules & Best Practices

Ok, let's talk about the basic rules of writing a PHPUnit test. First, you can technically put your test classes anywhere... but.. you've gotta admit that tests/ is a pretty good place. Actually, in a Symfony project, you automatically start with a phpunit.xml.dist file. Well, in Symfony 4, this is added when you install phpunit. We'll talk more about this file later... but PHPUnit reads this automatically. And... check it out! It says that our tests all live in... tests/. That's how PHPUnit is able to find our DinosaurTest.

Second, our test has a namespace... but that's not really important. In a Symfony project, your composer.json file has an autoload-dev section that basically says that anything in tests/ should start with the namespace Tests. No big deal.

Let's get to the really important stuff, because PHPUnit does have a few crucial rules. First, your test class must extend TestCase and end in the word Test. Second, all of your test methods must be public and start with the word test. When you run PHPUnit, it basically looks for all classes ending in Test and all public functions inside starting with test.

Got it? Good... because the storm is coming, and we've got work to do. Delete the fake test. Let's start coding.

Leave a comment!

While the fundamentals of PHPUnit haven't changed, this tutorial *is* built on an older version of Symfony and PHPUnit.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.0, <7.4",
        "doctrine/doctrine-bundle": "^1.6", // 1.10.3
        "doctrine/orm": "^2.5", // v2.7.2
        "incenteev/composer-parameter-handler": "^2.0", // v2.1.2
        "sensio/distribution-bundle": "^5.0.19", // v5.0.21
        "sensio/framework-extra-bundle": "^3.0.2", // v3.0.28
        "symfony/monolog-bundle": "^3.1.0", // v3.1.2
        "symfony/polyfill-apcu": "^1.0", // v1.6.0
        "symfony/swiftmailer-bundle": "^2.3.10", // v2.6.7
        "symfony/symfony": "3.3.*", // v3.3.13
        "twig/twig": "^1.0||^2.0" // v2.4.4
    },
    "require-dev": {
        "doctrine/data-fixtures": "^1.3", // 1.3.3
        "doctrine/doctrine-fixtures-bundle": "^2.3", // v2.4.1
        "liip/functional-test-bundle": "^1.8", // 1.8.0
        "phpunit/phpunit": "^6.3", // 6.5.2
        "sensio/generator-bundle": "^3.0", // v3.1.6
        "symfony/phpunit-bridge": "^3.0" // v3.4.30
    }
}