zz log

zaininnari Blog

テスト時のredirect()やcakeError() の結果を拾う。+ DebugKit使用時も。

前提

  • CakePHP 1.2.7 を使用。
    • cake testsuite app all のテストを対象
  • Core や App に手を加えたくない。
    • Core に手を入れると、アップデート時にマージを忘れる。
    • app_controller.php にテストの為のコードを書くと、毎回のオーバーヘッドが気になる。(テスト実行時でもないのに、テストの環境変数をチェックする処理は気になる)
  • DebugKit 使用時も使いたい。(SqlLogPanel::beforeRender() の ob_start() から結果が返らなくなる)
関連URL

やること

  • 関連URLの「CakeTestCaseでtestActionを実行すると別のページにリダイレクトされちゃう問題」のようにredirect() や cakeError() の処理を途中で中断させる。

具体的に。

  1. cake/console/cake を実行した際、cake/console/cake.php が呼ばれる
  2. 「testsuite」を処理するために、「testsuite.php」を呼ぼうとする
  3. App や Core 、vendors を順に探索して、「testsuite.php」を探す
  4. 独自に app/vendors/shells/testsuite.php を設置すると、cake/console/libs/testsuite.php の代わりに読み込まれる
  5. app/vendors/shells/testsuite.php では、独自に設置した app/libs/controller/controller.php を呼び、本来の testsuite.php を呼ぶ。
  6. app/libs/controller/controller.php では、 file_get_contents で、core/libs/controller/controller.php の内容を取得し、 redirect()、cakeError() の処理を置換し、コンポーネントの DebugKit を解除する。最後にeval()で読み込む。

Source

[app/vendors/shells/testsuite.php]

<?php

App::import(null, 'Controller', true, array(APP . 'libs/controller'));

foreach (array(VENDORS . 'shells/testsuite.php', CONSOLE_LIBS . 'testsuite.php') as $path) {
	if (file_exists($path)) {
		require $path;
		break;
	}
}

[app/libs/controller/controller.php]

<?php

$construct = '
if (defined("TEST_CAKE_CORE_INCLUDE_PATH")) {
	$index = array_search("DebugKit.Toolbar", $this->components);
	if ($index !== false) {
		unset($this->components[$index]);
		$this->components = array_merge(array_diff($this->components, array()));
	}
}
';

$cakeError = '
function cakeError($method, $messages = array())
{
	if (defined("TEST_CAKE_CORE_INCLUDE_PATH")) {
		$return = array(
			"function" => "cakeError",
			"method" => $method,
			"messages" => $messages,
			"session" => $this->Session->read()
		);
		$this->set("return", $return);
		$this->render(null, null, TESTS . "views" . DS . "redirect.ctp");
		return true;
	}
	return parent::cakeError($method, $messages);
}
';

$redirect = '
if (defined("TEST_CAKE_CORE_INCLUDE_PATH")) {
	$return = array(
		"function" => "redirect",
		"url" => $url,
		"status" => $status,
		"exit" => $exit,
		"session" => $this->Session->read()
	);
	$this->set("return", $return);
	$this->render(null, null, TESTS . DS . "views" . DS . "redirect.ctp");
	return true;
}
';

$unsetDebugKit = 'unset($this->components["DebugKit.Toolbar"]);';

$source = file_get_contents(LIBS . 'controller/controller.php');
$source = preg_replace('/^<\?php/', '', $source);
$source = preg_replace('/\?>$/', '', $source);
$source = preg_replace('/(function __construct\(.*\n)/', $cakeError . PHP_EOL . '$1' . $construct, $source);
$source = preg_replace('/(function redirect\(.*\n)/', '$1' . $redirect, $source);
$source = preg_replace('/(\$this->__mergeVars\(\);)/', '$1' . $unsetDebugKit, $source);

eval($source);

[app/tests/views/redirect.ctp]

<?php
echo json_encode($return);


[コントローラーのテスト内で]

<?php
	function testRedirect()
	{
		$result = $this->testAction('/users/view/1');
		$josn = json_decode($result, true);
		$function = $josn !== null ? Set::extract('function', $josn) : null;
		if ($function === 'cakeError') {
			$result = Set::extract('method', $josn);
		} elseif ($function === 'redirect') {
			$result = Set::extract('url', $josn);
		}
		$this->assertEqual($result , '/users/login');
	}