Last active
August 29, 2015 14:04
-
-
Save al-the-x/691f440c0edfe6a0485f to your computer and use it in GitHub Desktop.
I get tired of writing this simple `FunctionalTestCase` over and over again. I should submit to @sbergmann for PHPUnit...
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* A "functional" test is typically one that runs the full application through it's | |
* paces: constructing a request, triggering a route, generating a response, and | |
* testing the fully rendered output. This abstract class provides some assertions | |
* appropriate for testing requests and responses and abstract methods for actually | |
* fetching requests and responses and routing the application. The developer should | |
* extend this class for his or her specific framework or application. | |
*/ | |
abstract class FunctionalTestCase extends PHPUnit_Framework_TestCase | |
{ | |
static $HTTP_METHODS = [ 'get', 'post', 'put', 'delete', 'head', 'options', 'patch' ]; | |
/** | |
* Override in framework-specific classes to check the status of the response, | |
* as the API for doing so may differ between frameworks. | |
* | |
* @param integer $expected status code | |
* @param string|null $message to display on `fail()` | |
*/ | |
function assertStatus($expected, $message = null){ | |
$this->assertEquals($expected, $this->response()->getStatus(), $message); | |
} | |
/** | |
* Override in framework-specific classes to check a redirection in the response, | |
* as the API for doing so may differ between frameworks. | |
* | |
* @param string $expected URL for the redirect response | |
* @param integer $status code of the redirect, `302` by default | |
* @param string|null $message to display on `fail()` | |
*/ | |
function assertRedirect($expected, $status = 302, $message = null){ | |
$this->assertStatus($status, $message); | |
$this->assertEquals($expected, $this->responseHeaders('location'), $message); | |
} | |
/** | |
* @param string $expected XPath query | |
* @param integer $count to test against query | |
* @param string|null $message to display on `fail()` | |
*/ | |
function assertXPath($expected, $count = 1, $message = null){ | |
$query = $this->getDOMXPath()->query($expected); | |
$this->assertEquals($count, $query->length, $message); | |
} | |
/** | |
* @param string $expected XPath query | |
* @param string|null $message to display on `fail()` | |
*/ | |
function assertNotXpath($expected, $message = null){ | |
$this->assertXPath($expected, 0, $message); | |
} | |
/** | |
* Capture and return any output of `$callback` via output buffering... | |
* | |
* @param Closure $callback to invoke | |
* @return string captured output | |
*/ | |
function capture(Closure $callback){ | |
ob_start(); $callback(); return ob_get_clean(); | |
} | |
/** -- Framework-specific Utility Functions -- **/ | |
/** | |
* Return an appropriate "application instance" for each framework, e.g. `\Slim\Slim` for Slim | |
* or `Zend_Controller_Front` for ZF1. If there's a need to `$refresh` this instance, perhaps | |
* between tests, this method should support it. If the `$refresh` parameter is NOT passed, the | |
* instance should be cached for future invocations. | |
* | |
* @param boolean $refresh whether to (re)instantiate the application instance | |
* @return object representing the application instance | |
*/ | |
abstract function app($refresh = false); | |
/** | |
* Return an appropriate "request instance" for each framework, e.g. `\Slim\Http\Request` or | |
* `Zend_Http_Request` (ZF1). If there's a need to `$refresh` this instance, perhaps between | |
* tests, this method should support it. If the `$refresh` parameter is NOT passed, the instance | |
* should be cached for future invocations, probably on the `app()` instance. | |
* | |
* @param boolean $refresh whether to clear / rebuild the request instance | |
* @return object representing the request instance | |
*/ | |
abstract function request($refresh = false); | |
/** | |
* Get / set the request headers on the request instance appropriate to the application | |
* | |
* @param array $headers to set on the request | |
* @return mixed representation of the request headers | |
*/ | |
abstract function requestHeaders(array $headers = null); | |
/** | |
* Return an appropriate "response instance" for each framework, e.g. `\Slim\Http\Response` or | |
* `Zend_Http_Response` (ZF1). If there's a need to `$refresh` this instance, perhaps between | |
* tests, this method should support it. If the `$refresh` parameter is NOT passed, the instance | |
* should be cached for future invocations, probably on the `app()` instance. | |
* | |
* @param boolean $refresh whether to clear / rebuild the response instance | |
* @return object representing the response instance | |
*/ | |
abstract function response($refresh = false); | |
/** | |
* Get the headers from the response in whatever format makes sense for the application. Note | |
* that there's no setter for response headers, as these should be a side-effect of routing the | |
* application. Optionally, providing the name of a response header should return that specific | |
* header value. | |
* | |
* @param string $header name to retrieve | |
* @return mixed representation of the response headers appropriate to the application or a specific header value | |
*/ | |
abstract function responseHeaders($header = null); | |
/** | |
* Get the body of the response as a STRING for examination and use in XPath queries. | |
* | |
* @return string body of the response | |
*/ | |
abstract function responseBody(); | |
/** | |
* Invoke the application route with a mock / constructed request with the provided `$query` params | |
* and `$headers`. This method is a command and should only return `$this` for method chaining. | |
* | |
* @param string $method to route, e.g. GET, POST, PUT, DELETE | |
* @param string $path to route, e.g. "/path/to/resource", "/answer/42" | |
* @param array $query params to provide via the request | |
* @param array $headers to set on the request | |
* @return \FunctionalTestCase instance for method chaining, if appropriate | |
*/ | |
abstract function route($method, $path, array $query = [ ], array $headers = [ ]); | |
/** | |
* Shortcuts for `route()` based on recognized HTTP_METHODS, e.g. `$this->post('/path/to/resource', $data)`. | |
* | |
* @param string $method called against $this instance | |
* @array $arguments provided to $method, if any | |
* @throw \BadMethodCallException if $method isn't in HTTP_METHODS | |
*/ | |
function __call($method, array $arguments = [ ]){ | |
if ( ! in_array($method, static::$HTTP_METHODS) ){ | |
throw new \BadMethodCallException('Requested method does not exist: ' . $method); | |
} | |
list($path, $params, $headers) = array_pad($arguments, 3, [ ]); | |
return $this->route($method, $path, $params, $headers); | |
} | |
/** -- XPath Utility Methods -- **/ | |
/** | |
* @param boolean $refresh whether to refresh the DOMDocument instance | |
* @return \DOMDocument instance constructed from $this->response() | |
*/ | |
function getDOMDocument($refresh = false){ | |
static $instance; | |
if ( $refresh or !$instance ){ | |
$instance = $instance ?: new DOMDocument; | |
// DOMDocument::loadHTML complains if passed an empty string... | |
$this->responseBody() and $instance->loadHTML($this->responseBody()); | |
} | |
return $instance; | |
} | |
/** | |
* @param boolean $refresh whether to refresh the DOMXPath instance | |
* @return \DOMXPath instance constructed from $this->response() | |
*/ | |
function getDOMXPath($refresh = false){ | |
static $instance; | |
if ( $refresh or !$instance){ | |
$instance = new DOMXPath($this->getDOMDocument($refresh)); | |
} | |
return $instance; | |
} | |
} // END FunctionalTestCase | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
abstract class SlimFunctionalTestCase extends FunctionalTestCase | |
{ | |
/** | |
* @param boolean $refresh | |
* @return \Slim\Slim | |
*/ | |
function app($refresh = false){ | |
static $app; | |
$refresh and $app = app(); | |
return $app ?: app(); | |
} | |
/** | |
* `\Slim\Environment::mock()` is used to signal the `\Slim\Router` to | |
* dispatch a particular `\Slim\Route`. | |
* | |
* @param array $overrides to apply to the defaults | |
* @return \Slim\Environment | |
*/ | |
function env(array $overrides = [ ]){ | |
return \Slim\Environment::mock($overrides); | |
} | |
/** | |
* @param boolean $refresh | |
* @return \Slim\Http\Request | |
*/ | |
function request($refresh = false){ | |
return $this->app()->request(); | |
} | |
/** | |
* @param array $headers to set | |
* @return \Slim\Http\Headers for the request | |
*/ | |
function requestHeaders(array $headers = null){ | |
if ( ! is_null($headers) ){ | |
$this->request()->headers()->replace($headers); | |
} | |
return $this->request()->headers(); | |
} | |
/** | |
* @param boolean $refresh | |
* @return \Slim\Http\Response | |
*/ | |
function response($refresh = false){ | |
return $this->app()->response(); | |
} | |
/** | |
* @param string $header to return instead of ALL headers | |
* @return mixed \Slim\Http\Headers for the response or string $header value | |
*/ | |
function responseHeaders($header = null){ | |
$headers = $this->response()->headers(); | |
if ( is_null($header) ){ | |
return $headers; | |
} | |
// Technically this works, too, but only in later PHPs: | |
// return $this->response()->headers()[$header]; | |
return $headers[$header]; | |
} | |
/** | |
* @param string $method to route, e.g. POST, GET | |
* @param string $path to route, e.g. '/path/to/resource' | |
* @param array $query params to provide via the request | |
* @param array $hedaers to set on the request | |
* @return \FunctionalTestCase | |
*/ | |
function route($method, $path, array $query = [ ], array $headers = [ ]){ | |
// Slim extracts almost everything from the `\Slim\Environment`... | |
$this->env($options = array_merge([ | |
'REQUEST_METHOD' => strtoupper($method), | |
'PATH_INFO' => $path, | |
'SERVER_NAME' => 'testing', | |
'slim.input' => http_build_query($params) | |
], $headers)); | |
$app = $this->app(true); | |
// Because `\Slim\Slim::run()` produces output... | |
$this->capture(function() use ($app){ | |
$app->run(); | |
}); | |
$this->getDOMXPath(true); | |
return $this; | |
} | |
} // END SlimFunctionalTestCase |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment