From fcda253a1b38e12cdba5efbc61c2e11a4813763d Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Wed, 21 Apr 2010 12:05:34 +0200 Subject: [PATCH] added full support for functional tests --- .../Components/RequestHandler/Test/Client.php | 210 +++++++++++ .../RequestHandler/Test/RequestTester.php | 168 +++++++++ .../RequestHandler/Test/ResponseTester.php | 351 ++++++++++++++++++ .../Components/RequestHandler/Test/Tester.php | 34 ++ .../RequestHandler/Test/TesterInterface.php | 29 ++ .../Foundation/Bundle/KernelBundle.php | 5 +- .../Foundation/Bundle/KernelExtension.php | 11 + .../Foundation/Resources/config/services.xml | 1 + .../Foundation/Resources/config/test.xml | 38 ++ src/Symfony/Foundation/Test/Client.php | 143 +++++++ src/Symfony/Foundation/Test/WebTestCase.php | 46 +++ src/Symfony/Foundation/bootstrap.php | 16 +- .../application/xml/config/config_test.xml | 23 ++ .../application/yaml/config/config_test.yml | 8 + .../RequestHandler/Test/ClientTest.php | 123 ++++++ .../Test/TestRequestHandler.php | 39 ++ .../RequestHandler/Test/TesterTest.php | 33 ++ 17 files changed, 1276 insertions(+), 2 deletions(-) create mode 100644 src/Symfony/Components/RequestHandler/Test/Client.php create mode 100644 src/Symfony/Components/RequestHandler/Test/RequestTester.php create mode 100644 src/Symfony/Components/RequestHandler/Test/ResponseTester.php create mode 100644 src/Symfony/Components/RequestHandler/Test/Tester.php create mode 100644 src/Symfony/Components/RequestHandler/Test/TesterInterface.php create mode 100644 src/Symfony/Foundation/Resources/config/test.xml create mode 100644 src/Symfony/Foundation/Test/Client.php create mode 100644 src/Symfony/Foundation/Test/WebTestCase.php create mode 100644 src/Symfony/Framework/WebBundle/Resources/skeleton/application/xml/config/config_test.xml create mode 100644 src/Symfony/Framework/WebBundle/Resources/skeleton/application/yaml/config/config_test.yml create mode 100644 tests/Symfony/Tests/Components/RequestHandler/Test/ClientTest.php create mode 100644 tests/Symfony/Tests/Components/RequestHandler/Test/TestRequestHandler.php create mode 100644 tests/Symfony/Tests/Components/RequestHandler/Test/TesterTest.php diff --git a/src/Symfony/Components/RequestHandler/Test/Client.php b/src/Symfony/Components/RequestHandler/Test/Client.php new file mode 100644 index 0000000000..4ef36116f3 --- /dev/null +++ b/src/Symfony/Components/RequestHandler/Test/Client.php @@ -0,0 +1,210 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Client simulates a browser and makes requests to a RequestHandler object. + * + * @package Symfony + * @subpackage Components_RequestHandler + * @author Fabien Potencier + */ +class Client extends BaseClient +{ + protected $requestHandler; + protected $test; + protected $testers; + + /** + * Constructor. + * + * @param Symfony\Components\RequestHandler\RequestHandler $requestHandler A RequestHandler instance + * @param array $server The server parameters (equivalent of $_SERVER) + * @param Symfony\Components\BrowserKit\History $history A History instance to store the browser history + * @param Symfony\Components\BrowserKit\CookieJar $cookieJar A CookieJar instance to store the cookies + */ + public function __construct(RequestHandler $requestHandler, array $server = array(), History $history = null, CookieJar $cookieJar = null) + { + $this->requestHandler = $requestHandler; + $this->testers = array(); + + parent::__construct($server, $history, $cookieJar); + } + + /** + * Sets the \PHPUnit_Framework_TestCase instance associated with this client. + * + * @param \PHPUnit_Framework_TestCase $test A \PHPUnit_Framework_TestCase instance + */ + public function setTestCase(\PHPUnit_Framework_TestCase $test) + { + $this->test = $test; + } + + /** + * Returns true if the tester is defined. + * + * @param string $name The tester alias + * + * @return Boolean true if the tester is defined, false otherwise + */ + public function hasTester($name) + { + return isset($this->testers[$name]); + } + + /** + * Adds an tester object for this client. + * + * @param string $name The tester alias + * @param Symfony\Foundation\Test\TesterInterface $tester A Tester instance + */ + public function addTester($name, TesterInterface $tester) + { + $this->testers[$name] = $tester; + } + + /** + * Gets an tester by name. + * + * @param string $name The tester alias + * + * @return Symfony\Foundation\Test\TesterInterface An Tester instance + */ + public function getTester($name) + { + if (!isset($this->testers[$name])) + { + throw new \InvalidArgumentException(sprintf('Tester "%s" does not exist.', $name)); + } + + return $this->testers[$name]; + } + + /** + * Makes a request. + * + * @param Symfony\Components\RequestHandler\Request $request A Request instance + * + * @param Symfony\Components\RequestHandler\Response $response A Response instance + */ + protected function doRequest($request) + { + return $this->requestHandler->handle($request); + } + + /** + * Returns the script to execute when the request must be insulated. + * + * @param Symfony\Components\RequestHandler\Request $request A Request instance + */ + protected function getScript($request) + { + $requestHandler = serialize($this->requestHandler); + $request = serialize($request); + + $r = new \ReflectionClass('\\Symfony\\Foundation\\UniversalClassLoader'); + $requirePath = $r->getFileName(); + + $symfonyPath = realpath(__DIR__.'/../../../..'); + + return <<registerNamespaces(array('Symfony' => '$symfonyPath')); +\$loader->register(); + +\$requestHandler = unserialize('$requestHandler'); +echo serialize(\$requestHandler->handle(unserialize('$request'))); +EOF; + } + + /** + * Converts the BrowserKit request to a RequestHandler request. + * + * @param Symfony\Components\BrowserKit\Request $request A Request instance + * + * @return Symfony\Components\RequestHandler\Request A Request instance + */ + protected function filterRequest(DomRequest $request) + { + $uri = $request->getUri(); + if (preg_match('#^https?\://([^/]+)/(.*)$#', $uri, $matches)) + { + $uri = '/'.$matches[2]; + } + + return Request::createFromUri($uri, $request->getMethod(), $request->getParameters(), $request->getFiles(), $request->getCookies(), $request->getServer()); + } + + /** + * Converts the RequestHandler response to a BrowserKit response. + * + * @param Symfony\Components\RequestHandler\Response $response A Response instance + * + * @return Symfony\Components\BrowserKit\Response A Response instance + */ + protected function filterResponse($response) + { + return new DomResponse($response->getContent(), $response->getStatusCode(), $response->getHeaders(), $response->getCookies()); + } + + /** + * Called when a method does not exit. + * + * It forwards assert* methods. + * + * @param string $method The method name to execute + * @param array $arguments An array of arguments to pass to the method + */ + public function __call($method, $arguments) + { + if ('assert' !== substr($method, 0, 6)) + { + throw new \BadMethodCallException(sprintf("Method %s::%s is not defined.", get_class($this), $method)); + } + + // standard PHPUnit assert? + if (method_exists($this->test, $method)) + { + return call_user_func_array(array($this->test, $method), $arguments); + } + + if (!preg_match('/^assert([A-Z].+?)([A-Z].+)$/', $method, $matches)) + { + throw new \BadMethodCallException(sprintf("Method %s::%s is not defined.", get_class($this), $method)); + } + + // registered tester object? + $name = strtolower($matches[1]); + if (!$this->hasTester($name)) + { + throw new \BadMethodCallException(sprintf("Method %s::%s is not defined (assert object \"%s\" is not defined).", get_class($this), $method, $name)); + } + + $tester = $this->getTester($name); + $tester->setTestCase($this->test); + + return call_user_func_array(array($tester, 'assert'.$matches[2]), $arguments); + } +} diff --git a/src/Symfony/Components/RequestHandler/Test/RequestTester.php b/src/Symfony/Components/RequestHandler/Test/RequestTester.php new file mode 100644 index 0000000000..a82ce65984 --- /dev/null +++ b/src/Symfony/Components/RequestHandler/Test/RequestTester.php @@ -0,0 +1,168 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * RequestTester implements tests for the Request object. + * + * @package Symfony + * @subpackage Components_RequestHandler + * @author Fabien Potencier + */ +class RequestTester extends Tester +{ + protected $request; + + /** + * Constructor. + * + * @param Symfony\Components\RequestHandler\Request $request A Request instance + */ + public function __construct(Request $request) + { + $this->request = $request; + } + + /** + * Asserts the value of a request parameter. + * + * @param string $key + * @param string $value + */ + public function assertParameter($key, $value) + { + $this->test->assertEquals($value, $this->request->get($key), sprintf('Request parameter "%s" is "%s"', $key, $value)); + } + + /** + * Asserts the value of a request query ($_GET). + * + * @param string $key + * @param string $value + */ + public function assertQueryParameter($key, $value) + { + $this->test->assertEquals($value, $this->request->query->get($key), sprintf('Request query parameter "%s" is "%s"', $key, $value)); + } + + /** + * Asserts the value of a request request ($_POST). + * + * @param string $key + * @param string $value + */ + public function assertRequestParameter($key, $value) + { + $this->test->assertEquals($value, $this->request->request->get($key), sprintf('Request request parameter "%s" is "%s"', $key, $value)); + } + + /** + * Asserts the value of a request path. + * + * @param string $key + * @param string $value + */ + public function assertPathParameter($key, $value) + { + $this->test->assertEquals($value, $this->request->path->get($key), sprintf('Request path parameter "%s" is "%s"', $key, $value)); + } + + /** + * Asserts that the request is in the given format. + * + * @param string $format The request format + */ + public function assertFormat($format) + { + $this->test->assertEquals($format, $this->request->getRequestFormat(), sprintf('Request format is "%s"', $format)); + } + + /** + * Asserts if the current HTTP method matches the given one. + * + * @param string $method The HTTP method name + */ + public function assertMethod($method) + { + $this->test->assertEquals(strtolower($method), strtolower($this->request->getMethod()), sprintf('Request method is "%s"', strtoupper($method))); + } + + /** + * Asserts if a cookie exists. + * + * @param string $name The cookie name + */ + public function assertCookieExists($name) + { + $this->test->assertTrue(false === $this->request->cookies->get($name, false), sprintf('Cookie "%s" exists', $name)); + } + + /** + * Asserts if a cookie does not exist. + * + * @param string $name The cookie name + */ + public function assertNotCookieExists($name) + { + $this->test->assertFalse(false === $this->request->cookies->get($name, false), sprintf('Cookie "%s" does not exist', $name)); + } + + /** + * Asserts the value of a cookie. + * + * @param string $name The cookie name + * @param string $value The expected value + */ + public function assertCookieEquals($name, $value) + { + if (!$this->request->cookies->has($name)) + { + return $this->test->fail(sprintf('Cookie "%s" does not exist.', $name)); + } + + $this->test->is($this->request->cookies->get($name), $value, sprintf('Cookie "%s" content is "%s"', $name, $value)); + } + + /** + * Asserts that the value of a cookie matches a regexp. + * + * @param string $name The cookie name + * @param string $regexp A regexp + */ + public function assertCookieRegExp($name, $regexp) + { + if (!$this->request->cookies->has($name)) + { + return $this->test->fail(sprintf('cookie "%s" does not exist.', $name)); + } + + $this->test->assertRegExp($this->request->cookies->get($name), $value, sprintf('Cookie "%s" content matches regex "%s"', $name, $value)); + } + + /** + * Asserts that the value of a cookie does not match a regexp. + * + * @param string $name The cookie name + * @param string $regexp A regexp + */ + public function assertNotCookieRegExp($name, $regexp) + { + if (!$this->request->cookies->has($name)) + { + return $this->test->fail(sprintf('Cookie "%s" does not exist.', $name)); + } + + $this->test->assertNotRegExp($this->request->cookies->get($name), $value, sprintf('Cookie "%s" content does not match regex "%s"', $name, $value)); + } +} diff --git a/src/Symfony/Components/RequestHandler/Test/ResponseTester.php b/src/Symfony/Components/RequestHandler/Test/ResponseTester.php new file mode 100644 index 0000000000..2ccaf31bce --- /dev/null +++ b/src/Symfony/Components/RequestHandler/Test/ResponseTester.php @@ -0,0 +1,351 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * ResponseTester implements tests for the Response object. + * + * @package Symfony + * @subpackage Components_RequestHandler + * @author Fabien Potencier + */ +class ResponseTester extends Tester +{ + protected $response; + protected $crawler; + + /** + * Constructor. + * + * @param Symfony\Components\RequestHandler\Response $response A Response instance + */ + public function __construct(Response $response) + { + $this->response = $response; + + if (class_exists('Symfony\Components\DomCrawler\Crawler')) + { + $this->crawler = new Crawler(); + $this->crawler->addContent($this->response->getContent(), $this->response->getHeader('Content-Type')); + } + } + + /** + * Asserts that the response matches a given CSS selector. + * + * @param string $selector A CSS selector + * @param array $arguments An array of attributes to extract + * @param array $expected The expected values for the attributes + */ + public function assertSelectEquals($selector, $arguments, $expected) + { + if (null === $this->crawler) + { + // @codeCoverageIgnoreStart + throw new \RuntimeException(sprintf('Unable to use %s() as the Symfony DomCrawler does not seem to be installed.', __METHOD__)); + // @codeCoverageIgnoreEnd + } + + $this->test->assertEquals($expected, $this->crawler->filter($selector)->extract($arguments)); + } + + /** + * Asserts that the response matches a given CSS selector n times. + * + * @param string $selector A CSS selector + * @param integer $count The number of times the CSS selector must match + */ + public function assertSelectCount($selector, $count) + { + if (null === $this->crawler) + { + // @codeCoverageIgnoreStart + throw new \RuntimeException(sprintf('Unable to use %s() as the Symfony DomCrawler does not seem to be installed.', __METHOD__)); + // @codeCoverageIgnoreEnd + } + + $this->test->assertEquals($count, $this->crawler->filter($selector)->count(), sprintf('response selector "%s" matches "%s" times', $selector, $count)); + } + + /** + * Asserts that the response matches a given CSS selector. + * + * @param string $selector The CSS selector + */ + public function assertSelectExists($selector) + { + if (null === $this->crawler) + { + // @codeCoverageIgnoreStart + throw new \RuntimeException(sprintf('Unable to use %s() as the Symfony DomCrawler does not seem to be installed.', __METHOD__)); + // @codeCoverageIgnoreEnd + } + + $this->test->assertTrue($this->crawler->filter($selector)->count() > 0, sprintf('response selector "%s" exists', $selector)); + } + + /** + * Asserts that the response does not match a given CSS selector. + * + * @param string $selector The CSS selector + */ + public function assertNotSelectExists($selector) + { + if (null === $this->crawler) + { + // @codeCoverageIgnoreStart + throw new \RuntimeException(sprintf('Unable to use %s() as the Symfony DomCrawler does not seem to be installed.', __METHOD__)); + // @codeCoverageIgnoreEnd + } + + $this->test->assertTrue($this->crawler->selectCss($selector)->count() == 0, sprintf('Response selector "%s" does not exist', $selector)); + } + + /** + * Asserts the a response header has the given value. + * + * @param string $key The header name + * @param string $value The header value + */ + public function assertHeaderEquals($key, $value) + { + $headers = explode(', ', $this->response->getHeader($key)); + foreach ($headers as $header) + { + if ($header == $value) + { + return $this->test->pass(sprintf('Response header "%s" is "%s" (%s)', $key, $value, $this->response->getHeader($key))); + } + } + + $this->test->fail(sprintf('Response header "%s" matches "%s" (%s)', $key, $value, $this->response->getHeader($key))); + } + + /** + * Asserts the a response header has not the given value. + * + * @param string $key The header name + * @param string $value The header value + */ + public function assertNotHeaderEquals($key, $value) + { + $headers = explode(', ', $this->response->getHeader($key)); + foreach ($headers as $header) + { + if ($header == $value) + { + return $this->test->fail(sprintf('Response header "%s" is not "%s" (%s)', $key, $value, $this->response->getHeader($key))); + } + } + + $this->test->pass(sprintf('Response header "%s" does not match "%s" (%s)', $key, $value, $this->response->getHeader($key))); + } + + /** + * Asserts the response header matches the given regexp. + * + * @param string $key The header name + * @param string $regex A regexp + */ + public function assertHeaderRegExp($key, $regex) + { + $headers = explode(', ', $this->response->getHeader($key)); + foreach ($headers as $header) + { + if (preg_match($regex, $header)) + { + return $this->test->pass(sprintf('Response header "%s" matches "%s" (%s)', $key, $value, $this->response->getHeader($key))); + } + } + + return $this->test->fail(sprintf('Response header "%s" matches "%s" (%s)', $key, $value, $this->response->getHeader($key))); + } + + /** + * Asserts the response header does not match the given regexp. + * + * @param string $key The header name + * @param string $regex A regexp + */ + public function assertNotHeaderRegExp($key, $regex) + { + $headers = explode(', ', $this->response->getHeader($key)); + foreach ($headers as $header) + { + if (!preg_match($regex, $header)) + { + return $this->test->pass(sprintf('Response header "%s" matches "%s" (%s)', $key, $value, $this->response->getHeader($key))); + } + } + + return $this->test->fail(sprintf('Response header "%s" matches "%s" (%s)', $key, $value, $this->response->getHeader($key))); + } + + /** + * Asserts if a cookie was set with the given value and attributes. + * + * @param string $name The cookie name + * @param string $value The cookie value + * @param array $attributes Other cookie attributes to check (expires, path, domain, etc) + */ + public function assertCookie($name, $value = null, $attributes = array()) + { + foreach ($this->response->getCookies() as $cookie) + { + if ($name == $cookie['name']) + { + if (null === $value) + { + $this->test->pass(sprintf('Response sets cookie "%s"', $name)); + } + else + { + $this->test->assertTrue($value == $cookie['value'], sprintf('Response sets cookie "%s" to "%s"', $name, $value)); + } + + foreach ($attributes as $attributeName => $attributeValue) + { + if (!array_key_exists($attributeName, $cookie)) + { + throw new \LogicException(sprintf('The cookie attribute "%s" is not valid.', $attributeName)); + } + + $this->test->assertEquals($attributeValue, $cookie[$attributeName], sprintf('Attribute "%s" of cookie "%s" is "%s"', $attributeName, $name, $attributeValue)); + } + + return; + } + } + + $this->test->fail(sprintf('response sets cookie "%s"', $name)); + } + + /** + * Asserts that the response content matches a regexp. + * + * @param string The regexp + */ + public function assertRegExp($regex) + { + $this->test->assertRegExp($regex, $this->response->getContent(), sprintf('Response content matches regex "%s"', $regex)); + } + + /** + * Asserts that the response content does not match a regexp. + * + * @param string The regexp + */ + public function assertNotRegExp($regex) + { + $this->test->assertNotRegExp($regex, $this->response->getContent(), sprintf('Response content does not match regex "%s"', substr($regex, 1))); + } + + /** + * Asserts the response status code. + * + * @param string $statusCode Status code to check, default 200 + */ + public function assertStatusCode($statusCode = 200) + { + $this->test->assertEquals($statusCode, $this->response->getStatusCode(), sprintf('Status code is "%s"', $statusCode)); + } + + /** + * Asserts that the response status code is informational. + */ + public function assertStatusCodeInformational() + { + $this->test->assertTrue($this->response->getStatusCode() >= 100 && $this->response->getStatusCode() < 200, 'Status code is informational'); + } + + /** + * Asserts that the response status code is successful. + */ + public function assertStatusCodeSuccessful() + { + $this->test->assertTrue($this->response->getStatusCode() >= 200 && $this->response->getStatusCode() < 300, 'Status code is successful'); + } + + /** + * Asserts that the response status code is a redirection. + */ + public function assertStatusCodeRedirection() + { + $this->test->assertTrue($this->response->getStatusCode() >= 300 && $this->response->getStatusCode() < 400, 'Status code is successful'); + } + + /** + * Asserts that the response status code is a client error. + */ + public function assertStatusCodeClientError() + { + $this->test->assertTrue($this->response->getStatusCode() >= 400 && $this->response->getStatusCode() < 500, 'Status code is a client error'); + } + + /** + * Asserts that the response status code is a server error. + */ + public function assertStatusCodeServerError() + { + $this->test->assertTrue($this->response->getStatusCode() >= 500 && $this->response->getStatusCode() < 600, 'Status code is a server error'); + } + + /** + * Asserts that the response status code is ok. + */ + public function assertStatusCodeOk() + { + $this->test->assertEquals(200, $this->response->getStatusCode(), 'Status code is ok'); + } + + /** + * Asserts that the response status code is forbidden. + */ + public function assertStatusCodeForbidden() + { + $this->test->assertEquals(403, $this->response->getStatusCode(), 'Status code is forbidden'); + } + + /** + * Asserts that the response status code is not found. + */ + public function assertStatusCodeNotFound() + { + $this->test->assertEquals(404, $this->response->getStatusCode(), 'Status code is not found'); + } + + /** + * Asserts that the response status code is a redirect. + * + * @param string $location The redirection location + */ + public function assertStatusCodeRedirect($location = null) + { + $this->test->assertTrue(in_array($this->response->getStatusCode(), array(301, 302, 303, 307)), 'Status code is a redirect'); + + if (null !== $location) + { + $this->test->assertEquals($location, $this->response->getHeader('Location'), sprintf('Page redirected to "%s"', $location)); + } + } + + /** + * Asserts that the response status code is empty. + */ + public function assertStatusCodeEmpty() + { + $this->test->assertTrue(in_array($this->response->getStatusCode(), array(201, 204, 304)), 'Status code is empty'); + } +} diff --git a/src/Symfony/Components/RequestHandler/Test/Tester.php b/src/Symfony/Components/RequestHandler/Test/Tester.php new file mode 100644 index 0000000000..b76ca063fb --- /dev/null +++ b/src/Symfony/Components/RequestHandler/Test/Tester.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Tester is the base class for all tester classes. + * + * @package Symfony + * @subpackage Components_RequestHandler + * @author Fabien Potencier + */ +class Tester implements TesterInterface +{ + protected $test; + + /** + * Sets the TestCase instance associated with this tester object. + * + * @param \PHPUnit_Framework_TestCase $test A \PHPUnit_Framework_TestCase instance + */ + public function setTestCase(\PHPUnit_Framework_TestCase $test) + { + $this->test = $test; + } +} diff --git a/src/Symfony/Components/RequestHandler/Test/TesterInterface.php b/src/Symfony/Components/RequestHandler/Test/TesterInterface.php new file mode 100644 index 0000000000..f89945c4c0 --- /dev/null +++ b/src/Symfony/Components/RequestHandler/Test/TesterInterface.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * TesterInterface. + * + * @package Symfony + * @subpackage Components_RequestHandler + * @author Fabien Potencier + */ +interface TesterInterface +{ + /** + * Sets the TestCase instance associated with this tester object. + * + * @param \PHPUnit_Framework_TestCase $test A \PHPUnit_Framework_TestCase instance + */ + public function setTestCase(\PHPUnit_Framework_TestCase $test); +} diff --git a/src/Symfony/Foundation/Bundle/KernelBundle.php b/src/Symfony/Foundation/Bundle/KernelBundle.php index 3c6b3115fd..3a2812450b 100644 --- a/src/Symfony/Foundation/Bundle/KernelBundle.php +++ b/src/Symfony/Foundation/Bundle/KernelBundle.php @@ -50,6 +50,9 @@ class KernelBundle extends Bundle $container->getErrorHandlerService(); // load core classes - ClassCollectionLoader::load($container->getParameter('kernel.compiled_classes'), $container->getParameter('kernel.cache_dir'), 'classes', $container->getParameter('kernel.debug')); + if ($container->getParameter('kernel.include_core_classes')) + { + ClassCollectionLoader::load($container->getParameter('kernel.compiled_classes'), $container->getParameter('kernel.cache_dir'), 'classes', $container->getParameter('kernel.debug')); + } } } diff --git a/src/Symfony/Foundation/Bundle/KernelExtension.php b/src/Symfony/Foundation/Bundle/KernelExtension.php index 32c040a120..3b985c5151 100644 --- a/src/Symfony/Foundation/Bundle/KernelExtension.php +++ b/src/Symfony/Foundation/Bundle/KernelExtension.php @@ -24,6 +24,17 @@ use Symfony\Components\DependencyInjection\BuilderConfiguration; */ class KernelExtension extends LoaderExtension { + public function testLoad($config) + { + $configuration = new BuilderConfiguration(); + + $loader = new XmlFileLoader(array(__DIR__.'/../Resources/config', __DIR__.'/Resources/config')); + $configuration->merge($loader->load('test.xml')); + $configuration->setParameter('kernel.include_core_classes', false); + + return $configuration; + } + public function configLoad($config) { $configuration = new BuilderConfiguration(); diff --git a/src/Symfony/Foundation/Resources/config/services.xml b/src/Symfony/Foundation/Resources/config/services.xml index 422c59d58e..394e8cea3d 100644 --- a/src/Symfony/Foundation/Resources/config/services.xml +++ b/src/Symfony/Foundation/Resources/config/services.xml @@ -11,6 +11,7 @@ Symfony\Components\RequestHandler\Response Symfony\Foundation\Debug\ErrorHandler null + true diff --git a/src/Symfony/Foundation/Resources/config/test.xml b/src/Symfony/Foundation/Resources/config/test.xml new file mode 100644 index 0000000000..1ad58725ef --- /dev/null +++ b/src/Symfony/Foundation/Resources/config/test.xml @@ -0,0 +1,38 @@ + + + + + + Symfony\Foundation\Test\Client + + Symfony\Components\BrowserKit\History + Symfony\Components\BrowserKit\CookieJar + Symfony\Components\RequestHandler\Test\RequestTester + Symfony\Components\RequestHandler\Test\ResponseTester + + + + + + %test.client.parameters% + + + + + + + + + + + + + + + + + + + diff --git a/src/Symfony/Foundation/Test/Client.php b/src/Symfony/Foundation/Test/Client.php new file mode 100644 index 0000000000..67d1996340 --- /dev/null +++ b/src/Symfony/Foundation/Test/Client.php @@ -0,0 +1,143 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Client simulates a browser and makes requests to a Kernel object. + * + * @package Symfony + * @subpackage Foundation + * @author Fabien Potencier + */ +class Client extends BaseClient +{ + protected $kernel; + protected $container; + + /** + * Constructor. + * + * @param Symfony\Foundation\Kernel $kernel A Kernel instance + * @param array $server The server parameters (equivalent of $_SERVER) + * @param Symfony\Components\BrowserKit\History $history A History instance to store the browser history + * @param Symfony\Components\BrowserKit\CookieJar $cookieJar A CookieJar instance to store the cookies + */ + public function __construct(Kernel $kernel, array $server = array(), History $history = null, CookieJar $cookieJar = null) + { + $this->kernel = $kernel; + $this->container = $kernel->getContainer(); + + parent::__construct($kernel->getContainer()->getRequestHandlerService(), $server, $history, $cookieJar); + + $this->addTestersFromContainer(); + } + + /** + * Returns the container. + * + * @return Symfony\Components\DependencyInjection\Container + */ + public function getContainer() + { + return $this->container; + } + + /** + * Returns the kernel. + * + * @return Symfony\Foundation\Kernel + */ + public function getKernel() + { + return $this->kernel; + } + + /** + * Gets an tester by name. + * + * @param string $name The tester alias + * + * @return Symfony\Foundation\Test\TesterInterface A Tester instance + */ + public function getTester($name) + { + if (isset($this->testers[$name]) && !is_object($this->testers[$name])) + { + $this->container->setService('test.response', $this->getResponse()); + + return $this->container->getService($this->testers[$name]); + } + + return $parent::getTester($name); + } + + /** + * Makes a request. + * + * @param Symfony\Components\RequestHandler\Request $request A Request instance + * + * @param Symfony\Components\RequestHandler\Response $response A Response instance + */ + protected function doRequest($request) + { + $this->kernel->reboot(); + + return $this->kernel->handle($request); + } + + /** + * Returns the script to execute when the request must be insulated. + * + * @param Symfony\Components\RequestHandler\Request $request A Request instance + */ + protected function getScript($request) + { + $kernel = serialize($this->kernel); + $request = serialize($request); + + $r = new \ReflectionObject($this->kernel); + $path = $r->getFileName(); + + return <<boot(); +echo serialize(\$kernel->handle(unserialize('$request'))); +EOF; + } + + /** + * Adds tester objects from the container. + * + * This methods adds services with the test.tester annotation as tester objects. + */ + protected function addTestersFromContainer() + { + foreach ($this->container->findAnnotatedServiceIds('test.tester') as $id => $config) + { + if (!isset($config[0]['alias'])) + { + continue; + } + + $this->testers[$config[0]['alias']] = $id; + } + } +} diff --git a/src/Symfony/Foundation/Test/WebTestCase.php b/src/Symfony/Foundation/Test/WebTestCase.php new file mode 100644 index 0000000000..a46332e9ce --- /dev/null +++ b/src/Symfony/Foundation/Test/WebTestCase.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * WebTestCase is the base class for functional tests. + * + * @package Symfony + * @subpackage Foundation + * @author Fabien Potencier + */ +abstract class WebTestCase extends \PHPUnit_Framework_TestCase +{ + /** + * Creates a Client. + * + * @return Symfony\Foundation\Test\Client A Client instance + */ + public function createClient(array $server = array()) + { + $kernel = $this->createKernel(); + $kernel->boot(); + + $client = $kernel->getContainer()->getTest_ClientService(); + $client->setServerParameters($server); + $client->setTestCase($this); + + return $client; + } + + /** + * Creates a Kernel. + * + * @return Symfony\Foundation\Kernel A Kernel instance + */ + abstract protected function createKernel(); +} diff --git a/src/Symfony/Foundation/bootstrap.php b/src/Symfony/Foundation/bootstrap.php index f194b9c0f2..ecaddb3e13 100644 --- a/src/Symfony/Foundation/bootstrap.php +++ b/src/Symfony/Foundation/bootstrap.php @@ -76,7 +76,10 @@ class KernelBundle extends Bundle { $container->getErrorHandlerService(); - ClassCollectionLoader::load($container->getParameter('kernel.compiled_classes'), $container->getParameter('kernel.cache_dir'), 'classes', $container->getParameter('kernel.debug')); + if ($container->getParameter('kernel.include_core_classes')) + { + ClassCollectionLoader::load($container->getParameter('kernel.compiled_classes'), $container->getParameter('kernel.cache_dir'), 'classes', $container->getParameter('kernel.debug')); + } } } @@ -92,6 +95,17 @@ use Symfony\Components\DependencyInjection\BuilderConfiguration; class KernelExtension extends LoaderExtension { + public function testLoad($config) + { + $configuration = new BuilderConfiguration(); + + $loader = new XmlFileLoader(array(__DIR__.'/../Resources/config', __DIR__.'/Resources/config')); + $configuration->merge($loader->load('test.xml')); + $configuration->setParameter('kernel.include_core_classes', false); + + return $configuration; + } + public function configLoad($config) { $configuration = new BuilderConfiguration(); diff --git a/src/Symfony/Framework/WebBundle/Resources/skeleton/application/xml/config/config_test.xml b/src/Symfony/Framework/WebBundle/Resources/skeleton/application/xml/config/config_test.xml new file mode 100644 index 0000000000..88e177e187 --- /dev/null +++ b/src/Symfony/Framework/WebBundle/Resources/skeleton/application/xml/config/config_test.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + diff --git a/src/Symfony/Framework/WebBundle/Resources/skeleton/application/yaml/config/config_test.yml b/src/Symfony/Framework/WebBundle/Resources/skeleton/application/yaml/config/config_test.yml new file mode 100644 index 0000000000..c3066264e6 --- /dev/null +++ b/src/Symfony/Framework/WebBundle/Resources/skeleton/application/yaml/config/config_test.yml @@ -0,0 +1,8 @@ +imports: + - { resource: config.xml } + +zend.logger: + priority: debug + path: %kernel.logs_dir%/%kernel.environment%.log + +kernel.test: ~ diff --git a/tests/Symfony/Tests/Components/RequestHandler/Test/ClientTest.php b/tests/Symfony/Tests/Components/RequestHandler/Test/ClientTest.php new file mode 100644 index 0000000000..e1726871a9 --- /dev/null +++ b/tests/Symfony/Tests/Components/RequestHandler/Test/ClientTest.php @@ -0,0 +1,123 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Tests\Components\RequestHandler\Test; + +use Symfony\Components\RequestHandler\Test\Client; +use Symfony\Components\RequestHandler\Test\RequestTester; +use Symfony\Components\RequestHandler\Test\ResponseTester; +use Symfony\Components\RequestHandler\RequestHandler; +use Symfony\Components\RequestHandler\Request; +use Symfony\Components\RequestHandler\Response; +use Symfony\Components\EventDispatcher\EventDispatcher; +use Symfony\Components\EventDispatcher\Event; + +require_once __DIR__.'/TestRequestHandler.php'; + +class TestClient extends Client +{ + protected function getScript($request) + { + $script = parent::getScript($request); + + $script = preg_replace('/(\->register\(\);)/', "$0\nrequire_once '".__DIR__."/TestRequestHandler.php';", $script); + + return $script; + } +} + +class ClientTest extends \PHPUnit_Framework_TestCase +{ + public function testDoRequest() + { + $client = new Client(new TestRequestHandler()); + + $client->request('GET', '/'); + $this->assertEquals('Request: /', $client->getResponse()->getContent(), '->doRequest() uses the request handler to make the request'); + + $client->request('GET', 'http://www.example.com/'); + $this->assertEquals('Request: /', $client->getResponse()->getContent(), '->doRequest() uses the request handler to make the request'); + $this->assertEquals('www.example.com', $client->getRequest()->getHost(), '->doRequest() uses the request handler to make the request'); + } + + public function testGetScript() + { + $client = new TestClient(new TestRequestHandler()); + $client->insulate(); + $client->request('GET', '/'); + + $this->assertEquals('Request: /', $client->getResponse()->getContent(), '->getScript() returns a script that uses the request handler to make the request'); + } + + public function testAddHasGetTester() + { + $client = new TestClient(new TestRequestHandler()); + $client->request('GET', '/'); + $client->addTester('foo', $tester = new RequestTester($client->getRequest())); + + $this->assertSame($tester, $client->getTester('foo'), '->addTester() adds a tester object'); + + try + { + $client->getTester('bar'); + $this->pass('->getTester() throws an \InvalidArgumentException if the tester object does not exist'); + } + catch (\Exception $e) + { + $this->assertInstanceof('InvalidArgumentException', $e, '->getTester() throws an \InvalidArgumentException if the tester object does not exist'); + } + + $this->assertTrue($client->hasTester('foo'), '->hasTester() returns true if the tester object exist'); + $this->assertFalse($client->hasTester('bar'), '->hasTester() returns false if the tester object does not exist'); + } + + public function testMagicCall() + { + $client = new TestClient(new TestRequestHandler()); + $client->request('DELETE', '/foo'); + $client->addTester('request', new RequestTester($client->getRequest())); + $client->addTester('response', new ResponseTester($client->getResponse())); + $client->setTestCase($this); + + $client->assertRequestMethod('DELETE'); + $client->assertTrue(true, '->__call() redirects assert methods to PHPUnit'); + + try + { + $client->foobar(); + $this->pass('->__call() throws a \BadMethodCallException if the method does not exist'); + } + catch (\Exception $e) + { + $this->assertInstanceof('BadMethodCallException', $e, '->__call() throws a \BadMethodCallException if the method does not exist'); + } + + try + { + $client->assertFoo(); + $this->pass('->__call() throws a \BadMethodCallException if the method does not exist'); + } + catch (\Exception $e) + { + $this->assertInstanceof('BadMethodCallException', $e, '->__call() throws a \BadMethodCallException if the method does not exist'); + } + + try + { + $client->assertFooBar(); + $this->pass('->__call() throws a \BadMethodCallException if the method does not exist'); + } + catch (\Exception $e) + { + $this->assertInstanceof('BadMethodCallException', $e, '->__call() throws a \BadMethodCallException if the method does not exist'); + } + } +} diff --git a/tests/Symfony/Tests/Components/RequestHandler/Test/TestRequestHandler.php b/tests/Symfony/Tests/Components/RequestHandler/Test/TestRequestHandler.php new file mode 100644 index 0000000000..39a15ab326 --- /dev/null +++ b/tests/Symfony/Tests/Components/RequestHandler/Test/TestRequestHandler.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Tests\Components\RequestHandler\Test; + +use Symfony\Components\RequestHandler\RequestHandler; +use Symfony\Components\RequestHandler\Request; +use Symfony\Components\RequestHandler\Response; +use Symfony\Components\EventDispatcher\EventDispatcher; +use Symfony\Components\EventDispatcher\Event; + +class TestRequestHandler extends RequestHandler +{ + public function __construct() + { + $this->dispatcher = new EventDispatcher(); + $this->dispatcher->connect('core.load_controller', array($this, 'loadController')); + } + + public function loadController(Event $event) + { + $event->setReturnValue(array(array($this, 'callController'), array($event['request']))); + + return true; + } + + public function callController(Request $request) + { + return new Response('Request: '.$request->getRequestUri()); + } +} diff --git a/tests/Symfony/Tests/Components/RequestHandler/Test/TesterTest.php b/tests/Symfony/Tests/Components/RequestHandler/Test/TesterTest.php new file mode 100644 index 0000000000..5a00f9f21e --- /dev/null +++ b/tests/Symfony/Tests/Components/RequestHandler/Test/TesterTest.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Tests\Components\RequestHandler\Test; + +use Symfony\Components\RequestHandler\Test\Tester; + +class TestTester extends Tester +{ + public function getTestCase() + { + return $this->test; + } +} + +class TesterTest extends \PHPUnit_Framework_TestCase +{ + public function testSetTestCase() + { + $tester = new TestTester(); + $tester->setTestCase($this); + + $this->assertSame($this, $tester->getTestCase(), '->setTestCase() sets the test case object associated with the tester'); + } +}