Validation::uploadedFile()のアップデートで通らなくなったテストをカスタムバリデーションでどうにかする話

CakePHP 2.8.3、3.0.18、3.1.13、3.2.6 のリリースで、Validation::uploadedFile()が更新されました。
[CakePHP 2.8.3, 3.0.18, 3.1.13 and 3.2.6 Released](http://bakery.cakephp.org/2016/03/28/cakephp_283_3018_3113_326_released.html)

具体的には、`is_uploaded_file($file['tmp_name'])`のチェックが増えて、本当にPOSTでアップロードされたファイルなのかを確認するようになりました。

まぁそれはいいんですが、テストでファイルのアップロードを実行していたところが通らなくなってしまいました…。

今までこんな風にテストしていました。

```php:tests/TestCase/Controller/ArticlesControllerTest.php
public function testAdd()
{
    $data = [
        'title' => 'test title',
        'file1' => [
            'name' => 'picture.jpg',
            'type' => 'image/jpg',
            'tmp_name' => ROOT . DS . 'tests' . DS . 'Fixture' . DS . 'sample.jpg',
            'error' => UPLOAD_ERR_OK,
            'size' => 1475
        ]
    ];
    $this->post('/articles/add', $data);
    $this->assertRedirect(['controller' => 'articles', 'action' => 'index']);
}
```

Fixtureディレクトリに置いてあるsample.jpgファイルをPOSTしたようにしてテストしていたのですが、file1は以下のようにuploadedFileバリデーションを使っていたため、`is_uploaded_file()`で引っかかってバリデーションエラーが返るようになってしまいました。

```php:src/Model/Table/ArticlesTable.php
public function validationDefault(Validator $validator)
{
    $validator
        ->add('file1', 'image', [
            'rule' => ['uploadedFile', [
                'types' => Configure::read('image.allow_mime_types'),
                'maxSize' => Configure::read('image.max_size')
            ]],
            'message' => 'サイズが大きすぎるか、不正なファイルタイプです'
        ])
        ->allowEmpty('file1');
```

`is_uploaded_file()`を偽装(？)する方法はないものかと調べてみましたが、簡単にできそうになく(まぁそういうものかもしれませんが)、uploadedFileバリデーションをコピーしてカスタムバリデーションを作ることにしました。

```php:src/Model/Validation.php
<?php
namespace App\Model;

class Validation extends \Cake\Validation\Validation
{
    public static function uploadedFile($file, array $options = [])
    {
        $options += [
            'minSize' => null,
            'maxSize' => null,
            'types' => null,
            'optional' => false,
        ];
        if (!is_array($file)) {
            return false;
        }
        $keys = ['error', 'name', 'size', 'tmp_name', 'type'];
        ksort($file);
        if (array_keys($file) != $keys) {
            return false;
        }
        if (!static::uploadError($file, $options['optional'])) {
            return false;
        }
        if ($options['optional'] && (int)$file['error'] === UPLOAD_ERR_NO_FILE) {
            return true;
        }
        if (isset($options['minSize']) && !static::fileSize($file, '>=', $options['minSize'])) {
            return false;
        }
        if (isset($options['maxSize']) && !static::fileSize($file, '<=', $options['maxSize'])) {
            return false;
        }
        if (isset($options['types']) && !static::mimeType($file, $options['types'])) {
            return false;
        }
        if (defined('IS_TEST') && IS_TEST) {
            return true;
        }
        return is_uploaded_file($file['tmp_name']);
    }
}
```

最後のreturnの手前のIS_TESTをチェックしているところ以外は元のuploadedFileと同じです。
IS_TESTはtests/bootstrap.phpでdefineしておきます。
↓を追加。

```php:test/bootstrap.php
define('IS_TEST', true);
```

カスタムバリデーションを指定します。

```php:src/Model/Table/ArticlesTable.php
public function validationDefault(Validator $validator)
{
    $validator->provider('custom', 'App\Model\Validation');
    $validator
        ->add('file1', 'image', [
            'rule' => ['uploadedFile', [
                'types' => Configure::read('image.allow_mime_types'),
                'maxSize' => Configure::read('image.max_size')
            ]],
            'provider' => 'custom',
            'message' => 'サイズが大きすぎるか、不正なファイルタイプです'
        ])
        ->allowEmpty('file1');
```

これでテストの時は`is_uploaded_file()`をチェックしないようになったので、テストが通るようになりました。
なんかすっきりしないけど…。

# 追記 (2016/04/19)

コメントで教えてもらった方法で、元のソースに手を入れなくてもテストを通すことができました。

https://github.com/cakephp/cakephp/blob/master/tests/TestCase/Validation/stubs.php
↑をコピーして、tests/TestCase/Validation/stubs.phpというファイル(場所は特に決まってないと思いますが、同じようにしました)を作成し、該当のテスト(上記例だとtests/TestCase/Controller/ArticlesControllerTest.php)で

```php:tests/TestCase/Controller/ArticlesControllerTest.php
require_once TESTS . 'TestCase' . DS . 'Validation' . DS . 'stubs.php';
```

をuseの次ぐらいに書くだけです。すっきり！
名前空間上に関数が存在すればその関数を呼ぶなんて知りませんでした。