ci-phpunit-testを使う時に知っておいて欲しいCodeIgniter3の実装-Controllerの実装-コンストラクタ
Tweet私はしばし、ci-phpunit-testを使ったテストの書き方で、CodeIgniter3自体の挙動を説明しなければならないことがあります。
私は、その説明が非常にめんどくさいと感じているのでここに記載します。
知っておいて欲しい実装は次の4つです。
- Controllerの実装(設計)
- Controllerの実装(コンストラクタ) <- イマココ
- 依存性解決の実装
- Modelの実装
Controllerの実装(コンストラクタ)
コンストラクタの中では次のことを行います。
- 自身を静的変数に定義
- load_class関数を用いてロードしたクラスファイルをプロパティとして定義
- Loaderクラスをインスタンス化してプロパティとして定義
- Loaderクラスの初期化処理を実行
自身を静的変数に定義
Controllerでは、最初に自身を静的変数に代入します。
つまりは、new CI_Controllerを実行したインスタンスがget_instance
関数で取得できる様になります。
1 | $first = new CI_Controller(); |
この様に、最後にインスタンス化したControllerがget_instance
で取得できる様になります。
load_class関数を用いてロードしたクラスファイルをプロパティとして定義
このload_class関数については後述しますが、ここでは指定されたクラスをインスタンス化するnewのラップ関数と理解してください。
1 | load_class('Input', 'core') == new CI_Input |
CodeIgniterはControllerをインスタンス化するまでに様々なクラスをインスタンス化します。
その様々なクラスをインスタンス化する際は、load_class関数を用いてインスタンス化されます。
load_class関数を用いてインスタンス化されたオブジェクトをControllerのプロパティに代入します。
このような処理が、Constrollerのコンストラクタで行われています。
何もロードせずとも$this->input
などが利用できるのはこのためです。
Loaderクラスをインスタンス化してプロパティとして定義
Controllerをインスタンス化するときにLoaderクラスをインスタンス化し、プロパティとして定義しています。
Controllerを実装するときにparent::__construct
より前で$this->load
が利用できないのはこのためです。
また、非常に重要なポイントとしてLoaderクラスをインスタンス化するときはCommon.phpに定義されているload_class関数を利用しています。
Loaderクラスの初期化処理を実行
ここで、少しControllerと離れてしまいますが、Loaderクラスには初期化処理があります。
この初期化処理では、config/autoload.phpに記載されたModelやLibrary、Helperなどをロードします。
このautoloadに記載されたファイルをロードする際はLoaderに実装されているmodelメソッドやlibraryメソッドなどが利用されます。
ここまでのまとめ
- load_class関数を用いてインスタンス化されたオブジェクトをControllerのプロパティに代入する。
parent::__construct
より前で$this->load
は利用できない。- 最後にインスタンス化したControllerが
get_instance
で取得できる様になる。
テスト時に知っておきたいこと
load_class関数を用いてインスタンス化されたオブジェクトをControllerのプロパティに代入する
ci-phpunit-testにはload_class_instance
関数があります。
これは、load_classを用いてインスタンス化したことにする関数です。
つまり、load_class_instanceを用いてMockオブジェクトを挿入しておけば
Controllerのコンストラクタ内の処理で、Controllerのプロパティにmockオブジェクトが挿入されます。
1 | // コンストラクタが動くときに、ControllerのsampleプロパティへSample_dummyクラスが注入される。 |
parent::__construct
より前で$this->load
は利用できない
ci-phpunit-testにはrequest->setCallablePreConstructor
メソッドがあります。
これは、Controllerのインスタンス化前に処理を挟むことのできるメソッドです。
多くの場合load_class_instance関数と併用して利用されます。
また、インスタンス化前なので引数として与える関数では、テスト対象のコントローラへアクセスすることはできません。
この点がsetCallable
とは異なります。
1 |
|
また似たrequest->addCallablePreConstructor
もありますが、このメソッドはsetCallablePreConstructorでセットした関数を上書きしないで追加するメソッドです。
詳細は割愛します。
最後にインスタンス化したControllerがget_instance
で取得できる様になる
次の様なコードのテストを書く際は、どのControllerインスタンスが利用されているか意識すると良いです。
ライブラリ1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Todo_validator {
private $ci;
public function __construct()
{
$this->ci = get_instance();
}
public function is_valid(array $value): bool
{
$this->ci->form_validation->set_data($value);
$this->ci->form_validation->set_rules('title', 'タイトル', 'trim|required|maxlength[100]');
$this->ci->form_validation->set_rules('body', '本文', 'trim|required|maxlength[255]');
return $this->ci->form_validation->run();
}
}
テストコード1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Todo_test extends TestCase
{
public function test_controller_instance_not_equal()
{
$this->request->setCallablePreConstructor(function() {
// Todo_validatorがインスタンス化される時に呼び出されるget_instanceでは、
// テスト対象のコントローラインスタンス化前のフックなので
// Todo_test::classの$this->CIが取得される。
load_class_instance('todo_validator' new Todo_validator);
});
$this->request->setCallable(function($CI) {
// load_class_instanceで入れたTodo_validatorの$ciプロパティと
// setCallableの引数の$CIは違うインスタンスなのでこのMockオブジェクトは参照されない。
$this->form_validation = $this->getDouble(CI_Form_validation::class, [
'run' => true
]);
});
$output = $this->request('POST', 'todo/add', [
'title' => str_repeat('s', 1000),
'body' => 'body'
]);
// つまりこのテストは失敗する。
$this->assertRedirect('todo/complete');
}
}