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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy