postgresql - CakePHP 3: Prueba de unidad de modelo falla-"valor de clave duplicada"
unit-testing cakephp-3.0 (5)
¡Creo que finalmente he descubierto la solución REAL para este problema!
Creo que este problema se deriva de una configuración de dispositivo predeterminada que resulta del uso del comando bake
para generar dispositivos.
Cuando se bake
un modelo, se crea la placa de calderas para sus accesorios. ¿Observa el autoIncrement
para la propiedad ID en el código a continuación? Contrariamente a lo que podrías pensar, esto no debería ser true
. Cuando lo establezco en null
y elimino los id
de los elementos de la matriz $records
, ya no obtengo errores de exclusividad.
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,
...
Los magos ninja en el proyecto CakePHP son los héroes: fuente github.com/cakephp/cakephp/issues/8799
Estoy usando Postgres (que creo que está relacionado con el problema) y CakePHP 3.
Tengo la siguiente prueba de unidad para verificar que el modelo pueda guardar un conjunto de datos válido. Cuando ejecuto la siguiente prueba, con una prueba de unidad de modelo "horneada" estándar, obtengo el siguiente error.
Yo pienso que éste es el problema:
Estamos utilizando accesorios para agregar algunos datos base. Este es el único lugar donde creo que podría estar causando un problema. Para agregar credibilidad a esto, mientras se ejecutaban las pruebas de la unidad, ejecuté el siguiente comando para obtener el siguiente valor de id
incremento automático y devolvió 1, aunque devolvió el número correcto en la base de datos que no es de prueba. Select nextval(pg_get_serial_sequence(''agencies'', ''id'')) as new_id;
Prueba de unidad:
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);
}
Error:
PDOException: SQLSTATE[23505]: Unique violation: 7 ERROR: duplicate key value violates unique constraint "agencies_pkey"
DETAIL: Key (id)=(1) already exists.
Cosas que he probado
- Copió ese mismo código en un controlador y agregó exitosamente la entidad en la tabla.
- Añadiendo un ID de 200. Aparece el mismo error.
Actualización 1
El accesorio para esto tiene el campo ID establecido en cada registro. Eliminarlos del accesorio funciona, pero rompe otras pruebas de unidad que dependen de algunos datos relacionales.
No me gusta esta solución, pero agregar lo siguiente antes de guardar la entidad funciona.
$this->Agencies->deleteAll(''1=1'');
Podría ser un problema en la definición de su aparato. La documentación de Cake PHP utiliza un campo de _constraints
que especifica que el campo de id
es una clave principal:
''_constraints'' => [
''primary'' => [''type'' => ''primary'', ''columns'' => [''id'']],
]
Si los campos de id
se eliminan de los registros de aparatos, se utilizarán incrementos automáticos cuando se inserten, dejando la secuencia de ID de la tabla en el lugar correcto para las inserciones que ocurren durante las pruebas. Creo que es por eso que funciona para @emersonthis como se describe anteriormente.
Sin embargo, esa solución tiene otro problema: no puede crear relaciones confiables entre los registros de dispositivos porque no sabe qué ID obtendrán. ¿Qué coloca en el campo ID externo de una tabla relacionada? Esto me ha llevado a su solución original de solo alterar la secuencia de la tabla después de que se hayan insertado registros con ID codificados. Lo hago así en TestCases afectados ahora:
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)
)");
}
}
Esto mueve la secuencia de incremento automático a la ID más alta utilizada anteriormente. La próxima vez que se genere un ID a partir de la secuencia, será uno más alto, resolviendo el problema en todos los casos.
La inclusión de una de estas soluciones en un próximo lanzamiento de CakePHP se está discutiendo aquí .
[ACTUALIZACIÓN: Mi otra respuesta es la solución real a este problema.! Ya no tienes que hacer esto ...]
Aquí hay una solución alternativa menos sucia que no requiere eliminar todos los registros:
use Cake/Datasource/ConnectionManager;
...
$connection = ConnectionManager::get(''test'');
$results = $connection->execute(''ALTER SEQUENCE <tablename>_id_seq RESTART WITH 999999'');
//TEST WHICH INSERTS RECORD(s)...
Parece que el incremento automático no se configura / restablece correctamente durante setUp()
o tearDown()
... así que, al configurarlo manualmente en algo realmente alto (mayor que el número de registros existentes), se evita la "clave duplicada". .. "error.
El beneficio de este truco (sobre deleteAll(''1=1'')
) es que aún puede ejecutar pruebas que deleteAll(''1=1'')
referencia a datos de DB existentes.