IntegrationTestCase.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446
  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 = array();
  157. foreach ($env as $k => $v) {
  158. $envBackup[$k] = isset($_ENV[$k]) ? $_ENV[$k] : null;
  159. $_ENV[$k] = $v;
  160. }
  161. $result = $this->testAction($url, $options);
  162. foreach ($env as $k => $v) {
  163. $_ENV[$k] = $envBackup[$k];
  164. }
  165. $this->_response = $this->controller->response;
  166. $this->_request = $this->controller->request;
  167. $this->_requestSession = $this->controller->Session;
  168. return $result;
  169. }
  170. /**
  171. * Fetch a view variable by name.
  172. *
  173. * If the view variable does not exist null will be returned.
  174. *
  175. * @param string $name The view variable to get.
  176. * @return mixed The view variable if set.
  177. */
  178. public function viewVariable($name) {
  179. if (empty($this->controller->viewVars)) {
  180. $this->fail('There are no view variables, perhaps you need to run a request?');
  181. }
  182. if (isset($this->controller->viewVars[$name])) {
  183. return $this->controller->viewVars[$name];
  184. }
  185. return null;
  186. }
  187. /**
  188. * Assert that the response status code is in the 2xx range.
  189. *
  190. * @return void
  191. */
  192. public function assertResponseOk() {
  193. $this->_assertStatus(200, 204, 'Status code is not between 200 and 204');
  194. }
  195. /**
  196. * Assert that the response status code is in the 4xx range.
  197. *
  198. * @return void
  199. */
  200. public function assertResponseError() {
  201. $this->_assertStatus(400, 417, 'Status code is not between 400 and 417');
  202. }
  203. /**
  204. * Assert that the response status code is in the 5xx range.
  205. *
  206. * @return void
  207. */
  208. public function assertResponseFailure() {
  209. $this->_assertStatus(500, 505, 'Status code is not between 500 and 505');
  210. }
  211. /**
  212. * Asserts a specific response status code.
  213. *
  214. * @param int $code Status code to assert.
  215. * @return void
  216. */
  217. public function assertResponseCode($code) {
  218. $actual = $this->_response->statusCode();
  219. $this->_assertStatus($code, $code, 'Status code is not ' . $code . ' but ' . $actual);
  220. }
  221. /**
  222. * Helper method for status assertions.
  223. *
  224. * @param int $min Min status code.
  225. * @param int $max Max status code.
  226. * @param string $message The error message.
  227. * @return void
  228. */
  229. protected function _assertStatus($min, $max, $message) {
  230. if (!$this->_response) {
  231. $this->fail('No response set, cannot assert status code.');
  232. }
  233. $status = $this->_response->statusCode();
  234. $this->assertGreaterThanOrEqual($min, $status, $message);
  235. $this->assertLessThanOrEqual($max, $status, $message);
  236. }
  237. /**
  238. * Assert that the Location header is correct.
  239. *
  240. * @param string|array|null $url The URL you expected the client to go to. This
  241. * can either be a string URL or an array compatible with Router::url()
  242. * @param string $message The failure message that will be appended to the generated message.
  243. * @return void
  244. */
  245. public function assertRedirect($url = null, $message = '') {
  246. if (!$this->_response) {
  247. $this->fail('No response set, cannot assert location header. ' . $message);
  248. }
  249. $result = $this->_response->header();
  250. if ($url === null) {
  251. $this->assertTrue(!empty($result['Location']), $message);
  252. return;
  253. }
  254. if (empty($result['Location'])) {
  255. $this->fail('No location header set. ' . $message);
  256. }
  257. $this->assertEquals(Router::url($url, true), $result['Location'], $message);
  258. }
  259. /**
  260. * Asserts that the Location header is correct.
  261. *
  262. * @param string|array $url The url you expected the client to go to. This
  263. * can either be a string URL or an array compatible with Router::url()
  264. * @param string $message The failure message that will be appended to the generated message.
  265. * @return void
  266. */
  267. public function assertNoRedirect($message = '') {
  268. if (!$this->_response) {
  269. $this->fail('No response set, cannot assert location header. ' . $message);
  270. }
  271. $result = $this->_response->header();
  272. if (!$message) {
  273. $message = 'Redirect header set';
  274. }
  275. if (!empty($result['Location'])) {
  276. $message .= ': ' . $result['Location'];
  277. }
  278. $this->assertTrue(empty($result['Location']), $message);
  279. }
  280. /**
  281. * Assert response headers
  282. *
  283. * @param string $header The header to check
  284. * @param string $content The content to check for.
  285. * @param string $message The failure message that will be appended to the generated message.
  286. * @return void
  287. */
  288. public function assertHeader($header, $content, $message = '') {
  289. if (!$this->_response) {
  290. $this->fail('No response set, cannot assert headers. ' . $message);
  291. }
  292. $headers = $this->_response->header();
  293. if (!isset($headers[$header])) {
  294. $this->fail("The '$header' header is not set. " . $message);
  295. }
  296. $this->assertEquals($headers[$header], $content, $message);
  297. }
  298. /**
  299. * Assert content type
  300. *
  301. * @param string $type The content-type to check for.
  302. * @param string $message The failure message that will be appended to the generated message.
  303. * @return void
  304. */
  305. public function assertContentType($type, $message = '') {
  306. if (!$this->_response) {
  307. $this->fail('No response set, cannot assert content-type. ' . $message);
  308. }
  309. $alias = $this->_response->getMimeType($type);
  310. if ($alias !== false) {
  311. $type = $alias;
  312. }
  313. $result = $this->_response->type();
  314. $this->assertEquals($type, $result, $message);
  315. }
  316. /**
  317. * Assert content exists in the response body.
  318. *
  319. * @param string $content The content to check for.
  320. * @param string $message The failure message that will be appended to the generated message.
  321. * @return void
  322. */
  323. public function assertResponseEquals($content, $message = '') {
  324. if (!$this->_response) {
  325. $this->fail('No response set, cannot assert content. ' . $message);
  326. }
  327. $this->assertEquals($content, $this->_response->body(), $message);
  328. }
  329. /**
  330. * Assert content exists in the response body.
  331. *
  332. * @param string $content The content to check for.
  333. * @param string $message The failure message that will be appended to the generated message.
  334. * @return void
  335. */
  336. public function assertResponseContains($content, $message = '') {
  337. if (!$this->_response) {
  338. $this->fail('No response set, cannot assert content. ' . $message);
  339. }
  340. $this->assertContains($content, (string)$this->_response->body(), $message);
  341. }
  342. /**
  343. * Assert content does not exist in the response body.
  344. *
  345. * @param string $content The content to check for.
  346. * @param string $message The failure message that will be appended to the generated message.
  347. * @return void
  348. */
  349. public function assertResponseNotContains($content, $message = '') {
  350. if (!$this->_response) {
  351. $this->fail('No response set, cannot assert content. ' . $message);
  352. }
  353. $this->assertNotContains($content, (string)$this->_response->body(), $message);
  354. }
  355. /**
  356. * Assert that the search string was in the template name.
  357. *
  358. * @param string $content The content to check for.
  359. * @param string $message The failure message that will be appended to the generated message.
  360. * @return void
  361. */
  362. public function assertTemplate($content, $message = '') {
  363. if (!$this->_viewName) {
  364. $this->fail('No view name stored. ' . $message);
  365. }
  366. $this->assertContains($content, $this->_viewName, $message);
  367. }
  368. /**
  369. * Assert that the search string was in the layout name.
  370. *
  371. * @param string $content The content to check for.
  372. * @param string $message The failure message that will be appended to the generated message.
  373. * @return void
  374. */
  375. public function assertLayout($content, $message = '') {
  376. if (!$this->_layoutName) {
  377. $this->fail('No layout name stored. ' . $message);
  378. }
  379. $this->assertContains($content, $this->_layoutName, $message);
  380. }
  381. /**
  382. * Assert session contents
  383. *
  384. * @param string $expected The expected contents.
  385. * @param string $path The session data path. Uses Hash::get() compatible notation
  386. * @param string $message The failure message that will be appended to the generated message.
  387. * @return void
  388. */
  389. public function assertSession($expected, $path, $message = '') {
  390. if (empty($this->_requestSession)) {
  391. $this->fail('There is no stored session data. Perhaps you need to run a request?');
  392. }
  393. $result = $this->_requestSession->read($path);
  394. $this->assertEquals($expected, $result, 'Session content differs. ' . $message);
  395. }
  396. /**
  397. * Assert cookie values
  398. *
  399. * @param string $expected The expected contents.
  400. * @param string $name The cookie name.
  401. * @param string $message The failure message that will be appended to the generated message.
  402. * @return void
  403. */
  404. public function assertCookie($expected, $name, $message = '') {
  405. if (empty($this->_response)) {
  406. $this->fail('Not response set, cannot assert cookies.');
  407. }
  408. $result = $this->_response->cookie($name);
  409. $this->assertEquals($expected, $result['value'], 'Cookie data differs. ' . $message);
  410. }
  411. }