gorogoroyasu

福岡の開発会社で働いている。

"Key (id)=(1) already exists" error occurred in CakePHP3 model test.

久々にCakePHP でテストを書いている。
Fixture を整えて、TestCase を書いた。
そして、値を save するメソッドをテストした。

すると、

duplicate key value violates unique constraint "hoges_pkey"
DETAIL:  Key (id)=(1) already exists.

というエラーが出た。
悲しい。

書いてたFixture は、こんな感じ。

<?php
namespace App\Test\Fixture;

use Cake\TestSuite\Fixture\TestFixture;


class HogesFixture extends TestFixture
{

    /**
     * Fields
     *
     * @var array
     */
    // @codingStandardsIgnoreStart
    public $fields = [
        'id' => ['type' => 'integer', 'length' => 10, 'autoIncrement' => true, 'default' => null, 'null' => false, 'comment' => null, 'precision' => null, 'unsigned' => null],
        'user_id' => ['type' => 'integer', 'length' => 10, 'default' => null, 'null' => false, 'comment' => null, 'precision' => null, 'unsigned' => null, 'autoIncrement' => null],
        'fuga' => ['type' => 'text', 'length' => null, 'default' => null, 'null' => false, 'collate' => null, 'comment' => null, 'precision' => null],
        'modified' => ['type' => 'timestamp', 'length' => null, 'default' => null, 'null' => true, 'comment' => null, 'precision' => null],
        'deleted' => ['type' => 'timestamp', 'length' => null, 'default' => null, 'null' => true, 'comment' => null, 'precision' => null],
        '_constraints' => [
            'primary' => ['type' => 'primary', 'columns' => ['id'], 'length' => []],
        ],
    ];
    // @codingStandardsIgnoreEnd

    /**
     * Init method
     *
     * @return void
     */
    public function init()
    {
        $this->records = [
            [
                'id' => 1,
                'user_id' => 1,
                'fuga' => 'a',
                'modified' => 1539138087,
                'deleted' => 1539138087
            ],
            [
                'id' => 2,
                'user_id' => 1,
                'fuga' => 'b',
                'modified' => 1539138087,
                'deleted' => 1539138087
            ],
            [
                'id' => 3,
                'user_id' => 1,
                'fuga' => 'c',
                'modified' => 1539138087,
                'deleted' => 1539138087
            ],
        ];
        parent::init();
    }
}

そして、原因は Fixture にあった。

解決策は、

    public function init()
    {
        $this->records = [
            [
//                'id' => 1,
                'user_id' => 1,
                'fuga' => 'a',
                'modified' => 1539138087,
                'deleted' => 1539138087
            ],
            [
//                'id' => 2,
                'user_id' => 1,
                'fuga' => 'b',
                'modified' => 1539138087,
                'deleted' => 1539138087
            ],
            [
//                'id' => 3,
                'user_id' => 1,
                'fuga' => 'c',
                'modified' => 1539138087,
                'deleted' => 1539138087
            ],
        ];
        parent::init();
    }

$this->records に入れた連想配列内の、 id を消すこと。

普通に考えたらそうだ。
ID は、 データベースのsequenceの機能を使うので、idの発行をCakePHPに任せたら、 1からID が付与され、自動的にインクリメントされる。

一方、ID に値を入れてしまうと、Database の Sequence の機能を使わずに値が保存される。
よって、Database は、次の値は1だと思っている。しかし、1はすでに存在している。
だから、エラーになる。

細かいところだが、ハマりどころだ。

やっかいなことに、 Fixture を bake すると、デフォルトで値が挿入されるので、
Bake のテンプレートを書き換えるか (やったことない) いちいちコメントアウト (もしくは削除) する必要がある。

参考にしたのはこちら stackoverflow.com