IntegrationTestCase.php 14 KB

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