In the process of starting a brand new project here at DNSEE, me and my colleague Matteo decided – in order to make the whole team aware of how to test Symfony2 applications with PHPUnit – to port the Symfony2 functional testing mechanism into this project, which will be developed with symfony 1.X1.
Background
Lime – as you may know – is the officially-supported testing “framework”: it was specifically built to be used inside symfony 1.X applications and introduced lots of developers to the whole idea of testing in PHP.
It’s a lightweight and simple implementation of a testing framework, with poor support for mock objects, test doubles, data providers and test isolation, but it does its job.
Since Symfony2 decided to move to PHPUnit – a serious and more robust testing framework – suddenly all symfony developers needed to learn PHPUnit in order to test the new applications: this – at least – didn’t happened to me, because I heavily faced PHPUnit developing Orient, with lots of WTFs – mainly my fault.
So, starting this new project, I asked the team if they would agree on using PHPUnit to functionally test this new symfony 1.4 application, for 2 main reasons:
- learn PHPUnit, since the 3 developers involved in the project have worked for 10|4|0 months on PHPUnit ever
- get prepared for the big move, since Symfony2 uses an analogous testing mechanism
The Symfony2 way
In Symfony2 you basically instantiate your application with a fake client and make requests to it; at each request the application produces a response and a crawler lets you test the output:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
So, as you see, PHPUnit is used to make assertions on the response body2.
The basic idea
So, to backport the mechanism illustrated in the previous chapter to symfony 1.X, we should rely on a DOM crawler and a browser, capable of making HTTP requests and parse subquent responses’ bodies.
Fortunately, symfony 1.X’s functional testing mechanism already relies on an internal browser , able to bootstrap the application and make fake HTTP requests3, so we only need to integrate this browser into a PHPUnit test and parse responses with a crawler: since Symfony2 is a well-decoupled set of libraries we will use its DomCrawler component.
Implementation
First of all import the required libraries into your
symfony project; using SVN, we updated the lib/vendor
directory:
1 2 3 |
|
the content of the externals
property will be:
1 2 |
|
We are downloading the CssSelector component in order to use CSS selectors within the crawler: if you don’t want to use it you’ll need to write XPath queries to access the DOM nodes.
Save the externals
file and commit, then update the
lib/vendor/Symfony
directory in order to phisically
download the dependencies.
To finish the setup of the environment, create a phpunit.xml.dist
file in the root of the symfony project:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
|
and the test/bootstrap/autoloader.php
file, used by PHPUnit
for -guess it – autoloading classes:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
At this point the environment is ready, and you can start writing
your Symfony2’s correspondent WebTestCase
class4:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
So we’ve created a base class for every functional test we’ll write.
It consist in:
- a
createClient()
method which instantiates a new browser based on some configuration - an abstract method that each functional test need to implement in order
to setup the right application in the
createClient()
method (frontent, backend, whatever…)
The browser that we are using is sfPHPUnitBrowser
, instance of a
non-existing class, so let’s create it:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
This class extends the usual sfBrowser
one adding a simple functionality:
when a request is made, it does not return itself but an instance of a
Crawler
object.
This will let you do:
1 2 3 4 5 6 |
|
If you didn’t mistyped anything you should be able to create your first test:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
Next, create a route for your homepage and render some dummy template:
1
|
|
Now you can run the test with the usual phpunit
command:
The greatest benefit of this approach is that you can use
PHPUnit’s pure functionalities to test symfony 1.X
applications without re-inventing the wheel: what we saw
was the test of some output but bare in mind that, extending
sfBrowser
, our $client
object is able to access the request
and the user session too.
Why not re-using existing integrations?
Obviously, before writing any line of code, we took a look at existing PHPUnit’s integrations into symfony 1.X.
There are – basically – 2 plugins:
- sfPHPUnit2Plugin, which seemed useless being a PHPUnit wrapper for lime
- sfPHPUnitPlugin, which uses PHPUnit + Selenium, but we really don’t want to depend on a selenium instance to run our tests
- We are actually developing a few projects with Symfony2, mostly landing pages and small data-driven CRUD applications, due to the lack of comprehensive documentation about Symfony2, but I will flame about it in another post ↩
- This is not entirely true: PHPUnit is mainly used for testing the response, but inside a test-case you can access the user’s session, cookies and so on, therefore you can assert against lots of objects and use-cases ↩
- You can also use a real HTTP client to make requests to your application and test the output, but this approach is strongly discouraged because of dramatically-low performances ↩
- The WebTestCase is a base class for every functional test (in Symfony2), like PHPUnit_Framework_TestCase for canonical unit tests ↩