CakePHP 3: Модель Unit Test не работает - "дублирующее значение ключа"

Я использую Postgres (что, я думаю, связано с проблемой), и CakePHP 3.

У меня есть следующий unit test, чтобы просто проверить, может ли этот набор данных быть сохранен моделью. Когда я запускаю следующий тест, со стандартной "bake'd" Model unit test, я получаю ошибку ниже.

Я думаю, что это проблема:

Мы используем приборы для добавления некоторых базовых данных. Это единственное место, которое, я думаю, может вызвать проблемы. Чтобы добавить к этому достоверность, во время выполнения модульных тестов я выполнил следующую команду, чтобы получить следующее значение auto-incrementing id, и оно вернулось 1, даже если оно вернуло правильный номер в не-тестовой БД. Select nextval(pg_get_serial_sequence('agencies', 'id')) as new_id;

Unit Test:

public function testValidationDefault()
{
    $agencyData = [
        'full_name' => 'Agency Full Name',
        'mode' => 'transit',
        'request_api_class' => 'Rest\Get\Json',
        'response_api_class' => 'NextBus\Generic',
        'realtime_url_pattern' => 'http://api.example.com',
        'routes' => '{"123": {"full_route": "123 Full Route", "route_color": "#123456"}}'
    ];

    $agency = $this->Agencies->newEntity($agencyData);
    $saved = $this->Agencies->save($agency);
    $this->assertInstanceOf('App\Model\Entity\Agency', $saved);
}

Ошибка:

PDOException: SQLSTATE[23505]: Unique violation: 7 ERROR:  duplicate key value violates unique constraint "agencies_pkey"
DETAIL:  Key (id)=(1) already exists.

Что я пробовал

  • Скопировал этот же код в контроллер и успешно добавил объект в таблицу.
  • Добавление идентификатора 200. Появится такая же ошибка.

Обновление 1

У этого прибора есть поле ID, задающее каждую запись. Удаление их из прибора работает, но оно прерывает другие модульные тесты, которые полагаются на некоторые реляционные данные.

Ответы

Ответ 1

Мне не нравится это решение, но добавление следующего до сохранения объекта действительно работает.

$this->Agencies->deleteAll('1=1');

Ответ 2

[UPDATE: Мой другой ответ - это реальное решение этой проблемы! Вам больше не нужно это делать...]

Ниже приведено менее грязное обходное решение, которое не требует удаления всех записей:

use Cake\Datasource\ConnectionManager;
...
$connection = ConnectionManager::get('test');
$results = $connection->execute('ALTER SEQUENCE <tablename>_id_seq RESTART WITH 999999');
//TEST WHICH INSERTS RECORD(s)...

Похоже, что автоинкремент не устанавливается правильно / reset во время setUp() или tearDown()... поэтому вручную, устанавливая его на что-то действительно высокое (большее, чем количество существующих записей), предотвращает ошибка "дублировать ключ...".

Преимущество этого взлома (более deleteAll('1=1')) заключается в том, что вы можете впоследствии запускать тесты, которые ссылаются на существующие данные БД.

Ответ 3

Это может быть проблемой в определении вашего прибора. Cake PHP documentation использует поле _constraints, указывающее, что поле id является первичным ключом:

    '_constraints' => [
        'primary' => ['type' => 'primary', 'columns' => ['id']],
    ]

Ответ 4

Я полагаю, что, наконец, я решил РЕАЛЬНОЕ решение этой проблемы!

Я считаю, что эта проблема связана с настройкой прибора по умолчанию, которая возникает из-за использования команды bake для создания приборов.

Когда вы bake модель, он создает шаблон для этого светильника. Обратите внимание на autoIncrement для свойства ID в приведенном ниже коде? Вопреки тому, что вы могли бы подумать, это должно быть не true. Когда я устанавливаю его на null и удаляю id из элементов массива $records, я больше не получаю ошибки уникальности.

public $fields = [
    'id' => ['type' => 'integer', 'length' => 10, 'autoIncrement' => true, 'default' => null, 'null' => false, 'comment' => null, 'precision' => null, 'unsigned' => null],
    'nickname' => ['type' => 'text', 'length' => null, 'default' => null, 'null' => false, 'comment' => null, 'precision' => null],
...

public $records = [
    [
        // 'id' => 1,
        'nickname' => 'Foo bar',
        'width' => 800,
...

Мастера ниндзя в проекте CakePHP - это герои: источник билет CakePHP

Ответ 5

Если поля id удаляются из записей фиксации, они будут использовать автоматическое приращение при вставке, оставляя последовательность идентификаторов таблицы в нужном месте для вставок, которые происходят во время тестов. Я считаю, что именно поэтому он работает для @emersonthis, как описано выше.

У этого решения есть еще одна проблема: вы не можете создавать надежные отношения между записями приборов, потому что вы не знаете, какие идентификаторы они получат. Что вы помещаете в поле внешнего идентификатора связанной таблицы? Это привело меня к первоначальному решению просто изменить последовательность таблиц после того, как были вставлены записи с жестко закодированными идентификаторами. Сейчас я делаю это в затронутых TestCases:

public $fixtures = [
    'app.articles',
    'app.authors',
];

...

public function setUp()
{
    $connection = \Cake\Datasource\ConnectionManager::get('test');
    foreach ($this->fixtures as $fixture) {
        $tableName = explode('.', $fixture)[1];
        $connection->execute("
            SELECT setval(
                pg_get_serial_sequence('$tableName', 'id'),
                (SELECT MAX(id) FROM $tableName)
            )");
    }
}

Это приводит к тому, что последовательность автоматического увеличения увеличивается до самого высокого ранее использованного идентификатора. В следующий раз, когда идентификатор генерируется из последовательности, он будет один выше, разрешая проблему во всех случаях.

Включение одного из этих решений в предстоящий выпуск CakePHP обсуждается здесь.