IntegrationTestCase.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  1. <?php
  2. App::uses('MyControllerTestCase', 'Tools.TestSuite');
  3. App::uses('Router', 'Routing');
  4. App::uses('Dispatcher', 'Routing');
  5. App::uses('EventManager', 'Event');
  6. /**
  7. * A test case class intended to make integration tests of
  8. * your controllers easier.
  9. *
  10. * This class has been backported from 3.0.
  11. * Does not support cookies or non 2xx/3xx responses yet, though.
  12. *
  13. * This test class provides a number of helper methods and features
  14. * that make dispatching requests and checking their responses simpler.
  15. * It favours full integration tests over mock objects as you can test
  16. * more of your code easily and avoid some of the maintenance pitfalls
  17. * that mock objects create.
  18. */
  19. abstract class IntegrationTestCase extends MyControllerTestCase {
  20. /**
  21. * The data used to build the next request.
  22. * Use the headers key to set specific $_ENV headers.
  23. *
  24. * @var array
  25. */
  26. protected $_requestData = [];
  27. /**
  28. * Session data to use in the next request.
  29. *
  30. * @var array
  31. */
  32. protected $_sessionData = [];
  33. /**
  34. * Configure the data for the *next* request.
  35. *
  36. * This data is cleared in the tearDown() method.
  37. *
  38. * You can call this method multiple times to append into
  39. * the current state.
  40. *
  41. * @param array $data The request data to use.
  42. * @return void
  43. */
  44. public function configRequest(array $data) {
  45. $this->_requestData = $data + $this->_requestData;
  46. }
  47. /**
  48. * Set session data.
  49. *
  50. * This method lets you configure the session data
  51. * you want to be used for requests that follow. The session
  52. * state is reset in each tearDown().
  53. *
  54. * You can call this method multiple times to append into
  55. * the current state.
  56. *
  57. * @param array $data The session data to use.
  58. * @return void
  59. */
  60. public function session(array $data) {
  61. $this->_sessionData = $data + $this->_sessionData;
  62. }
  63. /**
  64. * Perform a GET request using the current request data.
  65. *
  66. * The response of the dispatched request will be stored as
  67. * a property. You can use various assert methods to check the
  68. * response.
  69. *
  70. * @param string $url The url to request.
  71. * @return void
  72. */
  73. public function get($url) {
  74. return $this->_sendRequest($url, 'GET');
  75. }
  76. /**
  77. * Perform a POST request using the current request data.
  78. *
  79. * The response of the dispatched request will be stored as
  80. * a property. You can use various assert methods to check the
  81. * response.
  82. *
  83. * @param string $url The url to request.
  84. * @param array $data The data for the request.
  85. * @return void
  86. */
  87. public function post($url, $data = []) {
  88. return $this->_sendRequest($url, 'POST', $data);
  89. }
  90. /**
  91. * Perform a PATCH request using the current request data.
  92. *
  93. * The response of the dispatched request will be stored as
  94. * a property. You can use various assert methods to check the
  95. * response.
  96. *
  97. * @param string $url The url to request.
  98. * @param array $data The data for the request.
  99. * @return void
  100. */
  101. public function patch($url, $data = []) {
  102. return $this->_sendRequest($url, 'PATCH', $data);
  103. }
  104. /**
  105. * Perform a PUT request using the current request data.
  106. *
  107. * The response of the dispatched request will be stored as
  108. * a property. You can use various assert methods to check the
  109. * response.
  110. *
  111. * @param string $url The url to request.
  112. * @param array $data The data for the request.
  113. * @return void
  114. */
  115. public function put($url, $data = []) {
  116. return $this->_sendRequest($url, 'PUT', $data);
  117. }
  118. /**
  119. * Perform a DELETE request using the current request data.
  120. *
  121. * The response of the dispatched request will be stored as
  122. * a property. You can use various assert methods to check the
  123. * response.
  124. *
  125. * @param string $url The url to request.
  126. * @return void
  127. */
  128. public function delete($url) {
  129. return $this->_sendRequest($url, 'DELETE');
  130. }
  131. /**
  132. * Create and send the request into a Dispatcher instance.
  133. *
  134. * Receives and stores the response for future inspection.
  135. *
  136. * @param string $url The url
  137. * @param string $method The HTTP method
  138. * @param array|null $data The request data.
  139. * @return mixed
  140. * @throws \Exception
  141. */
  142. protected function _sendRequest($url, $method, $data = []) {
  143. $options = array(
  144. 'data' => $data,
  145. 'method' => $method,
  146. 'return' => 'vars'
  147. );
  148. $env = array();
  149. if (isset($this->_requestData['headers'])) {
  150. foreach ($this->_requestData['headers'] as $k => $v) {
  151. $env['HTTP_' . str_replace('-', '_', strtoupper($k))] = $v;
  152. }
  153. unset($this->_requestData['headers']);
  154. }
  155. CakeSession::write($this->_sessionData);
  156. $envBackup = $serverBackup = array();
  157. foreach ($env as $k => $v) {
  158. $envBackup[$k] = isset($_ENV[$k]) ? $_ENV[$k] : null;
  159. $serverBackup[$k] = isset($_ENV[$k]) ? $_ENV[$k] : null;
  160. $_ENV[$k] = $v;
  161. $_SERVER[$k] = $v;
  162. }
  163. $result = $this->testAction($url, $options);
  164. foreach ($env as $k => $v) {
  165. $_ENV[$k] = $envBackup[$k];
  166. $_SERVER[$k] = $serverBackup[$k];
  167. }
  168. $this->_response = $this->controller->response;
  169. $this->_request = $this->controller->request;
  170. $this->_requestSession = $this->controller->Session;
  171. return $result;
  172. }
  173. /**
  174. * Fetch a view variable by name.
  175. *
  176. * If the view variable does not exist null will be returned.
  177. *
  178. * @param string $name The view variable to get.
  179. * @return mixed The view variable if set.
  180. */
  181. public function viewVariable($name) {
  182. if (empty($this->controller->viewVars)) {
  183. $this->fail('There are no view variables, perhaps you need to run a request?');
  184. }
  185. if (isset($this->controller->viewVars[$name])) {
  186. return $this->controller->viewVars[$name];
  187. }
  188. return null;
  189. }
  190. /**
  191. * Assert that the response status code is in the 2xx range.
  192. *
  193. * @return void
  194. */
  195. public function assertResponseOk() {
  196. $this->_assertStatus(200, 204, 'Status code is not between 200 and 204');
  197. }
  198. /**
  199. * Assert that the response status code is in the 4xx range.
  200. *
  201. * @return void
  202. */
  203. public function assertResponseError() {
  204. $this->_assertStatus(400, 417, 'Status code is not between 400 and 417');
  205. }
  206. /**
  207. * Assert that the response status code is in the 5xx range.
  208. *
  209. * @return void
  210. */
  211. public function assertResponseFailure() {
  212. $this->_assertStatus(500, 505, 'Status code is not between 500 and 505');
  213. }
  214. /**
  215. * Asserts a specific response status code.
  216. *
  217. * @param int $code Status code to assert.
  218. * @return void
  219. */
  220. public function assertResponseCode($code) {
  221. $actual = $this->_response->statusCode();
  222. $this->_assertStatus($code, $code, 'Status code is not ' . $code . ' but ' . $actual);
  223. }
  224. /**
  225. * Helper method for status assertions.
  226. *
  227. * @param int $min Min status code.
  228. * @param int $max Max status code.
  229. * @param string $message The error message.
  230. * @return void
  231. */
  232. protected function _assertStatus($min, $max, $message) {
  233. if (!$this->_response) {
  234. $this->fail('No response set, cannot assert status code.');
  235. }
  236. $status = $this->_response->statusCode();
  237. $this->assertGreaterThanOrEqual($min, $status, $message);
  238. $this->assertLessThanOrEqual($max, $status, $message);
  239. }
  240. /**
  241. * Assert that the Location header is correct.
  242. *
  243. * @param string|array|null $url The URL you expected the client to go to. This
  244. * can either be a string URL or an array compatible with Router::url()
  245. * @param string $message The failure message that will be appended to the generated message.
  246. * @return void
  247. */
  248. public function assertRedirect($url = null, $message = '') {
  249. if (!$this->_response) {
  250. $this->fail('No response set, cannot assert location header. ' . $message);
  251. }
  252. $result = $this->_response->header();
  253. if ($url === null) {
  254. $this->assertTrue(!empty($result['Location']), $message);
  255. return;
  256. }
  257. if (empty($result['Location'])) {
  258. $this->fail('No location header set. ' . $message);
  259. }
  260. $this->assertEquals(Router::url($url, true), $result['Location'], $message);
  261. }
  262. /**
  263. * Asserts that the Location header is correct.
  264. *
  265. * @param string|array $url The url you expected the client to go to. This
  266. * can either be a string URL or an array compatible with Router::url()
  267. * @param string $message The failure message that will be appended to the generated message.
  268. * @return void
  269. */
  270. public function assertNoRedirect($message = '') {
  271. if (!$this->_response) {
  272. $this->fail('No response set, cannot assert location header. ' . $message);
  273. }
  274. $result = $this->_response->header();
  275. if (!$message) {
  276. $message = 'Redirect header set';
  277. }
  278. if (!empty($result['Location'])) {
  279. $message .= ': ' . $result['Location'];
  280. }
  281. $this->assertTrue(empty($result['Location']), $message);
  282. }
  283. /**
  284. * Assert response headers
  285. *
  286. * @param string $header The header to check
  287. * @param string $content The content to check for.
  288. * @param string $message The failure message that will be appended to the generated message.
  289. * @return void
  290. */
  291. public function assertHeader($header, $content, $message = '') {
  292. if (!$this->_response) {
  293. $this->fail('No response set, cannot assert headers. ' . $message);
  294. }
  295. $headers = $this->_response->header();
  296. if (!isset($headers[$header])) {
  297. $this->fail("The '$header' header is not set. " . $message);
  298. }
  299. $this->assertEquals($headers[$header], $content, $message);
  300. }
  301. /**
  302. * Assert content type
  303. *
  304. * @param string $type The content-type to check for.
  305. * @param string $message The failure message that will be appended to the generated message.
  306. * @return void
  307. */
  308. public function assertContentType($type, $message = '') {
  309. if (!$this->_response) {
  310. $this->fail('No response set, cannot assert content-type. ' . $message);
  311. }
  312. $alias = $this->_response->getMimeType($type);
  313. if ($alias !== false) {
  314. $type = $alias;
  315. }
  316. $result = $this->_response->type();
  317. $this->assertEquals($type, $result, $message);
  318. }
  319. /**
  320. * Assert content exists in the response body.
  321. *
  322. * @param string $content The content to check for.
  323. * @param string $message The failure message that will be appended to the generated message.
  324. * @return void
  325. */
  326. public function assertResponseEquals($content, $message = '') {
  327. if (!$this->_response) {
  328. $this->fail('No response set, cannot assert content. ' . $message);
  329. }
  330. $this->assertEquals($content, $this->_response->body(), $message);
  331. }
  332. /**
  333. * Assert content exists in the response body.
  334. *
  335. * @param string $content The content to check for.
  336. * @param string $message The failure message that will be appended to the generated message.
  337. * @return void
  338. */
  339. public function assertResponseContains($content, $message = '') {
  340. if (!$this->_response) {
  341. $this->fail('No response set, cannot assert content. ' . $message);
  342. }
  343. $this->assertContains($content, (string)$this->_response->body(), $message);
  344. }
  345. /**
  346. * Assert content does not exist in the response body.
  347. *
  348. * @param string $content The content to check for.
  349. * @param string $message The failure message that will be appended to the generated message.
  350. * @return void
  351. */
  352. public function assertResponseNotContains($content, $message = '') {
  353. if (!$this->_response) {
  354. $this->fail('No response set, cannot assert content. ' . $message);
  355. }
  356. $this->assertNotContains($content, (string)$this->_response->body(), $message);
  357. }
  358. /**
  359. * Assert that the search string was in the template name.
  360. *
  361. * @param string $content The content to check for.
  362. * @param string $message The failure message that will be appended to the generated message.
  363. * @return void
  364. */
  365. public function assertTemplate($content, $message = '') {
  366. if (!$this->_viewName) {
  367. $this->fail('No view name stored. ' . $message);
  368. }
  369. $this->assertContains($content, $this->_viewName, $message);
  370. }
  371. /**
  372. * Assert that the search string was in the layout name.
  373. *
  374. * @param string $content The content to check for.
  375. * @param string $message The failure message that will be appended to the generated message.
  376. * @return void
  377. */
  378. public function assertLayout($content, $message = '') {
  379. if (!$this->_layoutName) {
  380. $this->fail('No layout name stored. ' . $message);
  381. }
  382. $this->assertContains($content, $this->_layoutName, $message);
  383. }
  384. /**
  385. * Assert session contents
  386. *
  387. * @param string $expected The expected contents.
  388. * @param string $path The session data path. Uses Hash::get() compatible notation
  389. * @param string $message The failure message that will be appended to the generated message.
  390. * @return void
  391. */
  392. public function assertSession($expected, $path, $message = '') {
  393. if (empty($this->_requestSession)) {
  394. $this->fail('There is no stored session data. Perhaps you need to run a request?');
  395. }
  396. $result = $this->_requestSession->read($path);
  397. $this->assertEquals($expected, $result, 'Session content differs. ' . $message);
  398. }
  399. /**
  400. * Assert cookie values
  401. *
  402. * @param string $expected The expected contents.
  403. * @param string $name The cookie name.
  404. * @param string $message The failure message that will be appended to the generated message.
  405. * @return void
  406. */
  407. public function assertCookie($expected, $name, $message = '') {
  408. if (empty($this->_response)) {
  409. $this->fail('Not response set, cannot assert cookies.');
  410. }
  411. $result = $this->_response->cookie($name);
  412. $this->assertEquals($expected, $result['value'], 'Cookie data differs. ' . $message);
  413. }
  414. }