gstreamer0.10-ffmpeg
gstreamer0.10-plugins-good
packages.
When you make a pull request to Symfony, you almost always need at least one test. And... yea... we definitely need a test for our new TargetPathHelper
.
But, before we start writing it... shouldn't we first figure out how to run Symfony's tests? Great idea! And I'm happy to report that it's quite easy.
Look in the symfony/
directory. It has a composer.json
file that describes all of the libraries that Symfony itself needs in order to work and in order to run its tests.
Move over to your terminal and run:
composer update
There's one important difference between a reusable library like Symfony and a normal application: Symfony does not have a composer.lock
file! We commit the composer.json
file to Symfony, but we do not commit composer.lock
. Why not? Well, there's just no point. Individual apps that require Symfony will lock Symfony at some version in their app. But, when we're working on Symfony itself, we usually want the latest version of all of its dependencies.
So before you run your tests, make sure to run composer update
. Running composer install
isn't good enough, because there could already be a composer.lock
file from an earlier time you ran composer install
. Running update makes sure you have the latest stuff for whatever branch of Symfony you're currently on.
Perfect! Now we do have a composer.lock
file.
Ok, we're ready to run the tests! Do it with:
./phpunit
Um... that's it! This is a wrapper around PHPUnit: it downloads some dependencies to a different directory, then... starts running the tests! And... yea... there are a lot of tests. I'm going to stop these by pressing Ctrl
+C
.
To be honest, I never run the full test suite locally. You just don't need to! As you'll see in a few minutes, Symfony has a robust continuous integration setup: when you make a pull request, Symfony's test suite is run automatically.
Thanks to that, locally, I usually just run the tests I'm working on. Let's test everything in SecurityBundle:
./phpunit src/Symfony/Bundle/SecurityBundle
This time... if you didn't fast forward like me... you'd see that these tests only take a minute or two. There are a few "skipped" tests: that's probably not something you need to worry about. Some tests require a special PHP extension or some other service that your local computer might not have. So, those tests are skipped. No big deal.
Now that the tests are running, it's time to add our own! I'll double-click to get back into SecurityBundle. Because we want to test TargetPathHelper
, the test should live in Tests/Security
. Create a new PHP class called TargetPathHelperTest
. Make this extend the normal TestCase
from PHPUnit:
... lines 1 - 2 | |
namespace Symfony\Bundle\SecurityBundle\Tests\Security; | |
use PHPUnit\Framework\TestCase; | |
class TargetPathHelperTest extends TestCase | |
{ | |
... lines 9 - 12 | |
} |
Then add public function testSavePath()
:
... lines 1 - 2 | |
namespace Symfony\Bundle\SecurityBundle\Tests\Security; | |
use PHPUnit\Framework\TestCase; | |
class TargetPathHelperTest extends TestCase | |
{ | |
public function testSavePath() | |
{ | |
} | |
} |
For the body of the test... yea... I'm going to cheat. This isn't a testing tutorial, so I'll paste in some code I already prepared:
... lines 1 - 4 | |
use PHPUnit\Framework\TestCase; | |
use Symfony\Bundle\SecurityBundle\Security\FirewallConfig; | |
use Symfony\Bundle\SecurityBundle\Security\FirewallMap; | |
use Symfony\Bundle\SecurityBundle\Security\TargetPathHelper; | |
use Symfony\Component\HttpFoundation\Request; | |
use Symfony\Component\HttpFoundation\RequestStack; | |
use Symfony\Component\HttpFoundation\Session\SessionInterface; | |
class TargetPathHelperTest extends TestCase | |
{ | |
public function testSavePath() | |
{ | |
$session = $this->createMock(SessionInterface::class); | |
$firewallMap = $this->createMock(FirewallMap::class); | |
$requestStack = $this->createMock(RequestStack::class); | |
$request = new Request(); | |
$requestStack->expects($this->once()) | |
->method('getMasterRequest') | |
->willReturn($request); | |
$firewallConfig = new FirewallConfig('firewall_name', ''); | |
$firewallMap->expects($this->once()) | |
->method('getFirewallConfig') | |
->with($request) | |
->willReturn($firewallConfig); | |
$session->expects($this->once()) | |
->method('set') | |
->with('_security.firewall_name.target_path', '/foo'); | |
$targetPathHelper = new TargetPathHelper($session, $firewallMap, $requestStack); | |
$targetPathHelper->savePath('/foo'); | |
} | |
} |
Oh, and I need to auto-complete a few things to get the missing use
statements, like FirewallMap
from SecurityBundle, and a few other ones:
... lines 1 - 4 | |
use PHPUnit\Framework\TestCase; | |
use Symfony\Bundle\SecurityBundle\Security\FirewallConfig; | |
use Symfony\Bundle\SecurityBundle\Security\FirewallMap; | |
use Symfony\Bundle\SecurityBundle\Security\TargetPathHelper; | |
use Symfony\Component\HttpFoundation\Request; | |
use Symfony\Component\HttpFoundation\RequestStack; | |
use Symfony\Component\HttpFoundation\Session\SessionInterface; | |
... lines 12 - 39 |
Our TargetPathHelper
class doesn't really do much: it pushes most of the work back to the methods from the trait. So, this test basically creates a bunch of mocks, creates a FirewallConfig
that returns a firewall name of, um, firewall_name
, and then we ultimately make sure that this special key is set on the session to the URL we passed to savePath()
.
If you're interested in understanding this test better, you can totally look into it more. But, the beautiful part is that creating a unit test for Symfony is no different than creating a unit test for an application: there's no framework code here.
Let's go run this one test directly:
./phpunit src/Symfony/Bundle/SecurityBundle/Tests/Security/TargetPathHelperTest.php
The last step is to register our new class as a service and enable it to be autowired. Let's get to it!
Hey Martin,
Yes, we don't have any explicit assertions, but PHPUnit mock system do some assertions for us, e.g. we expect that getMasterRequest() and getFirewallConfig() methods are called and called exactly *once*, etc. So, if one of those methods was not called, or was called more than once - PHPUnit would show the test as failed. So, it's kinda assertions behind the scene.
I'd recommend you to re-watch information about PHPUnit mocks to get more context about it.
Cheers!
Why this test does not have any assertions? I'm new to testing but still...