Skip to content

Commit f00401d

Browse files
committed
added part 8
1 parent 5674395 commit f00401d

File tree

1 file changed

+189
-0
lines changed

1 file changed

+189
-0
lines changed

book/part08.rst

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
Create your own framework... on top of the Symfony2 Components (part 8)
2+
=======================================================================
3+
4+
Some watchful readers pointed out some subtle but nonetheless important bugs
5+
in the framework we have built yesterday. When creating a framework, you must
6+
be sure that it behaves as advertised. If not, all the applications based on
7+
it will exhibit the same bugs. The good news is that whenever you fix a bug,
8+
you are fixing a bunch of applications too.
9+
10+
Today's mission is to write unit tests for the framework we have created by
11+
using `PHPUnit`_. Create a PHPUnit configuration file in
12+
``example.com/phpunit.xml.dist``:
13+
14+
.. code-block:: xml
15+
16+
<?xml version="1.0" encoding="UTF-8"?>
17+
18+
<phpunit backupGlobals="false"
19+
backupStaticAttributes="false"
20+
colors="true"
21+
convertErrorsToExceptions="true"
22+
convertNoticesToExceptions="true"
23+
convertWarningsToExceptions="true"
24+
processIsolation="false"
25+
stopOnFailure="false"
26+
syntaxCheck="false"
27+
bootstrap="vendor/.composer/autoload.php"
28+
>
29+
<testsuites>
30+
<testsuite name="Test Suite">
31+
<directory>./tests</directory>
32+
</testsuite>
33+
</testsuites>
34+
</phpunit>
35+
36+
This configuration defines sensible defaults for most PHPUnit settings; more
37+
interesting, the autoloader is used to bootstrap the tests, and tests will be
38+
stored under the ``example.com/tests/`` directory.
39+
40+
Now, let's write a test for "not found" resources. To avoid the creation of
41+
all dependencies when writing tests and to really just unit-test what we want,
42+
we are going to use `test doubles`_. Test doubles are easier to create when we
43+
rely on interfaces instead of concrete classes. Fortunately, Symfony2 provides
44+
such interfaces for core objects like the URL matcher and the controller
45+
resolver. Modify the framework to make use of them::
46+
47+
<?php
48+
49+
// example.com/src/Simplex/Framework.php
50+
51+
namespace Simplex;
52+
53+
// ...
54+
55+
use Symfony\Component\Routing\Matcher\UrlMatcherInterface;
56+
use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface;
57+
58+
class Framework
59+
{
60+
protected $matcher;
61+
protected $resolver;
62+
63+
public function __construct(UrlMatcherInterface $matcher, ControllerResolverInterface $resolver)
64+
{
65+
$this->matcher = $matcher;
66+
$this->resolver = $resolver;
67+
}
68+
69+
// ...
70+
}
71+
72+
We are now ready to write our first test::
73+
74+
<?php
75+
76+
// example.com/tests/Simplex/Tests/FrameworkTest.php
77+
78+
namespace Simplex\Tests;
79+
80+
use Simplex\Framework;
81+
use Symfony\Component\HttpFoundation\Request;
82+
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
83+
84+
class FrameworkTest extends \PHPUnit_Framework_TestCase
85+
{
86+
public function testNotFoundHandling()
87+
{
88+
$framework = $this->getFrameworkForException(new ResourceNotFoundException());
89+
90+
$response = $framework->handle(new Request());
91+
92+
$this->assertEquals(404, $response->getStatusCode());
93+
}
94+
95+
protected function getFrameworkForException($exception)
96+
{
97+
$matcher = $this->getMock('Symfony\Component\Routing\Matcher\UrlMatcherInterface');
98+
$matcher
99+
->expects($this->once())
100+
->method('match')
101+
->will($this->throwException($exception))
102+
;
103+
$resolver = $this->getMock('Symfony\Component\HttpKernel\Controller\ControllerResolverInterface');
104+
105+
return new Framework($matcher, $resolver);
106+
}
107+
}
108+
109+
This test simulates a request that does not match any route. As such, the
110+
``match()`` method returns a ``ResourceNotFoundException`` exception and we
111+
are testing that our framework converts this exception to a 404 response.
112+
113+
Executing this test is as simple as running ``phpunit`` from the
114+
``example.com`` directory:
115+
116+
.. code-block:: bash
117+
118+
$ phpunit
119+
120+
After the test ran, you should see a green bar. If not, you have a bug
121+
either in the test or in the framework code!
122+
123+
Adding a unit test for any exception thrown in a controller is just as easy::
124+
125+
public function testErrorHandling()
126+
{
127+
$framework = $this->getFrameworkForException(new \RuntimeException());
128+
129+
$response = $framework->handle(new Request());
130+
131+
$this->assertEquals(500, $response->getStatusCode());
132+
}
133+
134+
Last, but not the least, let's write a test for when we actually have a proper
135+
Response::
136+
137+
use Symfony\Component\HttpFoundation\Response;
138+
use Symfony\Component\HttpKernel\Controller\ControllerResolver;
139+
140+
public function testControllerResponse()
141+
{
142+
$matcher = $this->getMock('Symfony\Component\Routing\Matcher\UrlMatcherInterface');
143+
$matcher
144+
->expects($this->once())
145+
->method('match')
146+
->will($this->returnValue(array(
147+
'_route' => 'foo',
148+
'name' => 'Fabien',
149+
'_controller' => function ($name) {
150+
return new Response('Hello '.$name);
151+
}
152+
)))
153+
;
154+
$resolver = new ControllerResolver();
155+
156+
$framework = new Framework($matcher, $resolver);
157+
158+
$response = $framework->handle(new Request());
159+
160+
$this->assertEquals(200, $response->getStatusCode());
161+
$this->assertContains('Hello Fabien', $response->getContent());
162+
}
163+
164+
In this test, we simulate a route that matches and returns a simple
165+
controller. We check that the response status is 200 and that its content is
166+
the one we have set in the controller.
167+
168+
To check that we have covered all possible use cases, run the PHPUnit test
169+
coverage feature (you need to enable `XDebug`_ first):
170+
171+
.. code-block:: bash
172+
173+
phpunit --coverage-html=cov/
174+
175+
Open ``example.com/cov/src_Simplex_Framework.php.html`` in a browser and check
176+
that all the lines for the Framework class are green (it means that they have
177+
been visited when the tests were executed).
178+
179+
Thanks to the simple object-oriented code that we have written so far, we have
180+
been able to write unit-tests to cover all possible use cases of our
181+
framework; test doubles ensured that we were actually testing our code and not
182+
Symfony2 code.
183+
184+
Now that we are confident (again) about the code we have written, we can
185+
safely think about the next batch of features we want to add to our framework.
186+
187+
.. _`PHPUnit`: http://www.phpunit.de/manual/current/en/index.html
188+
.. _`test doubles`: http://www.phpunit.de/manual/current/en/test-doubles.html
189+
.. _`XDebug`: http://xdebug.org/

0 commit comments

Comments
 (0)