IntegrationTestCase.php 14 KB

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