CakePHP 3: Модульный тест модели не пройден - «дубликат значения ключа»

17

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

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

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

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

Модульный тест:

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, устанавливающее каждую запись. Удаление их из устройства работает, но оно нарушает другие модульные тесты, основанные на некоторых реляционных данных.

    
задан Chris Mielke 12.11.2015 в 19:29
источник
  • Ошибка здесь: github.com/cakephp/cakephp/issues/8799 –  emersonthis 09.05.2016 в 20:50

5 ответов

4

Мне не нравится это решение, но добавление следующего перед сохранением сущности работает.

$this->Agencies->deleteAll('1=1');
    
ответ дан Chris Mielke 13.11.2015 в 14:59
  • Я попробую это, но его очень грязный ... Кажется, это ошибка в CakePHP? Кажется, что база данных не усечена на tearDown ... –  Bob 06.01.2016 в 20:38
4

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

Вот менее грязный обходной путь, который не требует удаления всех записей:

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

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

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

    
ответ дан emersonthis 14.01.2016 в 20:08
  • Вы посмотрели мое предложение? –  oliverpool 19.01.2016 в 21:39
3

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

    '_constraints' => [
        'primary' => ['type' => 'primary', 'columns' => ['id']],
    ]
    
ответ дан oliverpool 19.01.2016 в 12:23
  • это, кажется, самый разумный ответ до сих пор, стоит попробовать –  Gang 20.01.2016 в 02:02
  • Извините за поздний ответ. Эта проблема возникает, несмотря на наличие этого ключа в массиве $ fields. –  emersonthis 09.05.2016 в 20:50
1

Мне кажется, я наконец-то нашел РЕАЛЬНОЕ решение этой проблемы!

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

Когда вы bake модели, она создает шаблон для своих приборов. Обратите внимание на autoIncrement для свойства ID в приведенном ниже коде? Вопреки тому, что вы думаете, это должно не , а true . Когда я устанавливаю значение null и удаляю id s из элементов массива $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

    
ответ дан emersonthis 11.05.2016 в 01:40
1

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

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

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 обсуждается здесь .

    
ответ дан Ethan 17.11.2016 в 07:35