2014年5月29日木曜日

[CakePHP]Controllerのテストで主要モデル以外のモデルのモックを作る方法

※CakePHP2.4.9の話

例えば、AmazonsControllerのテストの場合、

AmazonsControllerTest.php内
$this->controller = $this->generate('Amazons', array(
    'models' => array(
        'Amazon' => array('hardToTest')
    )
));
$this->controller->Amazon->expects($this->once())
        ->method('hardToTest')
        ->will($this->returnValue(true));

主要モデル(コントローラー名に名前が入るモデル)のAmazonモデルの場合は、こんな感じでモックを生成できる。(hardToTestというメソッドのモックを作りたい時)

でも、それ以外のモデル、例えば、Amazonモデルにアソシエーションで紐づくモデルやControllerの処理内でloadModel()したモデル、さらにそれにアソシエーションに紐づくモデルetcはこのやりかたで生成できない。

上記でいう、6行目のexpects()で下記のエラーが発生する。

Error: Call to a member function expects() on a non-object

これに対処する方法のひとつにテスト対象のControllerの$usesにモックを生成したいモデルを入れる、というものもある。ようは、モック化対象のモデルを主要モデルの一つにしちゃうという方法だ。これなら上記のような形で生成できる。
でもそもそもテストのためにプロダクトのコードに手を入れるのってやだよね。

そんな場合は、getMockForModel()を使う。これで簡単にControllerのテスト内で任意のモデルのモックを生成できる。

$this->Controller = $this->generate('Amazons');
$this->getMockForModel('Vote', array('hardToTest'))
        ->expects($this->any())->method('hardToTest');

上記で登場するVoteモデルはちなみにAmazonモデルには直接アソシエーションで紐づいていないモデルだ。Controllerの処理内でloadModelされるItemというModelにアソシエーションで紐づいているモデルである。

でも上記のコードでモックを生成することができる。
注意点としては、かならず1行目でControllerのモックを生成しておく必要があること。

余談だが、上記の$this->Controllerて変数はどんな変数名にしてもOK。$Amazonsにしてもいいし$aでも$bでもOK。generate()メソッドは自動的に$this->Controllerを生成してくれるみたいです。※参考 CakePHP 2.xのtestActionをちゃんと理解しよう

もう一つ余談ですが、当然ですが、モック化したメソッド以外のメソッドは本物のメソッド使ってくれるみたいですね。

2014年5月26日月曜日

[CakePHP]deleteAllはcounterCache更新してくれないみたいね。

※CakePHP2.4.9の話

これは古い記事だけど、こういう対処法があるんだね。

counterCache and deleteAll: How to Make Them Friends

※追記
すみません。引数でcallbackを動かすか指定できるので、それをtrueにするとcoutercacheも更新されます。

http://book.cakephp.org/2.0/en/models/deleting-data.html#deleteall

2014年5月18日日曜日

[Bootstrap]collapseでクリックしたらdata-targetではなくhrefを使うべし。

※Bootstrap 3.1.1のお話。

まず、http://getbootstrap.com/javascript/#collapseでは、以下のように説明されていて、開閉したい要素のidをdata-targetで指定するように書いてある。
たとえば data-target="#demo" といったような感じだ。

Via data attributes
Just add data-toggle="collapse" and a data-target to element to automatically assign control of a collapsible element. The data-target attribute accepts a CSS selector to apply the collapse to. Be sure to add the class collapse to the collapsible element. If you'd like it to default open, add the additional classin.

しかし、iphone5cで試すと、開閉しない。

クリック(iphoneだとtap)する要素がbuttonタグの場合や、classに.btnなどがついている場合、type="button"がついている場合は開閉する。しかし、ただのaタグの場合は開閉しない。

aタグでtype="button"やclass="btn"をつけたくない場合は、data-target="#demo"ではなく、href="#demo"をつけるとうまく動く。
両方つけてしまうと、対象の#demoが画面トップにくようスクロールしてしまう。



2014年5月13日火曜日

[CakePHP]Validation の'required' => trueは外部キーにはいれないほうがいい。

なんでかというと、$Model->saveAssociated()等でassociation先のモデルと同時に保存する場合、外部キーを自動的に補ってくれる。
でもvalidationの処理が走るのは、外部キーを補う前なので、'required' => trueになってしまい、validation errorが発生してしまうから。


※CakePHP2.4.9の話

[CakePHP]Validationってそもそも配列にfieldの添字がないと発火しないんだね。

※CakePHP2.4.9のお話

何が言いたいかって、例えば以下のようなvalidationを設定している時。

public $validate = array(
  'user_id' =>array(
    'rule' => 'notEmpty',
    'message' => 'このフィールドは必ず入力してください。',
  ),
  ),
);


下記みたいにvalidation対象のuser_idが無い配列を保存しようとしたら、notEmptyが発生すると思ていたんだけど、発生せずに保存しちゃった。user_idにはDBでフォルトの0が入っていた。

array(
  'theme_id'=> '22'
)

要するに、validationするには対象のフィールドの添字が配列内に存在しないと発火しないのね。

※追記
色々実験してわかったけど、以下みたいに'required' => trueを入れると添字がない場合にvalidation errorを発生させてくれる。
public $validate = array(
  'user_id' =>array(
    'rule' => 'notEmpty',
    'message' => 'このフィールドは必ず入力してください。',
    'required' => true,
  ),
  ),
);


'required' => trueの不思議なのって、どのvalidationの種類にも入れられるので、例えばemailの形式を求めるvalidationに入れた場合、メッセージは「emailの形式で入れて下さい」みたいに出るんだよね。notEmptyだったらわりと自然なメッセージになるけど。


2014年5月7日水曜日

[CakePHP]Fatal Error Error: Cannot redeclare class ThemesItemFixture

CakePHPでブラウザ上からのUNITテストをしていたらこんなエラーが出ました。

いくつかのテストファイルをまとめている「All tests」実行時のみ発生しました。
単体でそれぞれでテストを実行した場合は発生しませんでした。

以下のように修正したら直りました。なんか衝突してたみたいですね。
単体のファイルの実行だとどちらもOKというのが不思議な感じがしますが。

before
public $fixtures = array(
    'app.themesitem'
);


afiter
public $fixtures = array(
    'app.themes_item'
);



2014年5月6日火曜日

[CakePHP]外部サービスのAPIを使ったdatasourceのテスト

自分はAmazon AssociateのAPIを使ったデータソースを作った。
でもこのあたりのテストを書こうとしてたら問題がおこった。

MISSINGTABLEEXCEPTION

Table amazons for model Amazon was not found in datasource test.

こんなエラーが発生した。

以下を参考にしたら解決策がわかった。
[PHP] CakePHP の $useDbConfig にはまった(備忘録)
フィクスチャの作成

単純にdatabase.php内に作成したデータソースの接続設定に対応する設定を作ってあげればOKです。

例えば、database.php内に以下のようなデータソースの設定を追加したなら、

public $amazon = array(
  'datasource' => 'AmazonSource'
);


合わせてdatabase.php内に以下のようなデータソースを設定する必要があります。

public $test_amazon = array(
  'datasource' => 'AmazonSource'
);

変数名は上記のように、$test_[テストしたい設定名]にしなくてはなりません。
これで終わりです。fixtureを作成する必要もありません。
あとはテストを書くだけです。