# « Undefined array key » dans une usine de fixtures partagée entre projets

> Une factory commune à plusieurs projets suppose des clés toujours présentes. Un client les omet, et la suite de tests explose en silence.

- Auteur : WordPress Développement
- Publié le : 2021-12-20
- Mis à jour le : 2021-12-20
- Catégorie : Tests
- URL : https://www.wpmoderne.fr/tests/undefined-array-key-factory-partagee-projets/

## L’essentiel

- Une factory partagée fige des hypothèses qui ne tiennent pas partout
- Un accès direct à un tableau casse dès qu'une clé optionnelle manque
- La correction passe par des valeurs par défaut explicites, pas par des rustines locales

`PHP Warning: Undefined array key "siret" in /app/tests/Factory/OrganisationFactory.php on line 34`. C'est ce message, répété dans les logs de trois suites différentes le même après-midi, qui a mis en évidence un problème qu'on traînait depuis des mois : une factory de fixtures partagée entre plusieurs projets d'agence, écrite pour le premier client qui en avait eu besoin, et jamais pensée pour les suivants.

La factory en question générait des organisations de test pour un plugin d'adhésion associative. Elle avait été copiée telle quelle dans un second projet, puis un troisième, chacun ajoutant ses propres champs métier sans jamais remettre en question la structure de base. Le jour où un client n'utilisait pas de SIRET (une collectivité, identifiée par un code INSEE), la factory a explosé silencieusement dans certains tests et bruyamment dans d'autres.

## Le symptôme : des tests verts qui cachent un problème

Le pire dans cette histoire, ce n'est pas l'avertissement PHP visible dans les logs de CI. C'est que la moitié des tests continuaient à passer malgré l'avertissement, parce que PHP transforme silencieusement la clé manquante en `null` puis en chaîne vide dans les assertions concernées. Résultat : des tests qui vérifient en réalité `'' === ''` au lieu de comparer une vraie valeur de SIRET.

- Le rapport de couverture indiquait 91 % alors que des branches entières de validation n'étaient jamais exercées correctement.
- Les revues de code ne voyaient rien d'anormal : la factory « fonctionnait », au sens où elle ne plantait pas le build.
- Le bug n'est apparu qu'en recette, quand un testeur humain a rempli un vrai formulaire sans SIRET.

## Le diagnostic : une factory pensée pour un seul contexte

En creusant, la cause était simple : la factory construisait son tableau de données avec des accès directs, du type `$data['siret']`, sans jamais vérifier la présence de la clé ni prévoir de valeur de repli. Chaque projet qui l'avait réutilisée avait ajouté ses champs par-dessus sans toucher au cœur, empilant les hypothèses implicites les unes sur les autres.

> L'essentiel à retenir : Une factory partagée fige des hypothèses qui ne tiennent pas partout ; Un accès direct à un tableau casse dès qu'une clé optionnelle manque ; La correction passe par des valeurs par défaut explicites, pas par des rustines locales

Un data provider PHPUnit permet de faire varier des jeux de données à l'intérieur d'un même test ; il ne résout rien ici, puisque le problème se situe en amont, dans la construction même de l'objet de test partagé entre plusieurs suites et plusieurs projets.

### Le correctif : des valeurs par défaut explicites

La réparation a consisté à réécrire la factory pour qu'elle expose ses hypothèses au lieu de les cacher :

```
final class OrganisationFactory
{
    private const DEFAULTS = [
        'nom'        => 'Organisation de test',
        'siret'      => null,
        'code_insee' => null,
        'email'      => 'contact@example.test',
    ];

    public static function create(array $overrides = []): array
    {
        $data = array_merge(self::DEFAULTS, $overrides);

        if ($data['siret'] === null && $data['code_insee'] === null) {
            throw new \InvalidArgumentException(
                'Une organisation de test doit avoir un identifiant (siret ou code_insee).'
            );
        }

        return $data;
    }
}
```

La clé `array_merge` avec un tableau de constantes fait tout le travail : plus aucun accès direct au tableau brut, et toute clé manquante prend explicitement la valeur `null` plutôt que de déclencher un avertissement. L'exception ajoutée oblige chaque appelant à fournir un identifiant valide, ce qui rend visible dans les tests eux-mêmes les hypothèses métier qu'on faisait auparavant en silence.

## Prévenir la récidive

Corriger une factory ne suffit pas si les six projets qui la partagent continuent à en dépendre par copier-coller. La vraie solution a été d'en faire un petit paquet Composer interne, versionné indépendamment, avec ses propres tests unitaires sur les cas limites : organisation sans SIRET, sans e-mail, avec des overrides partiels.

- Ajouter `error_reporting(E_ALL)` strict dans le bootstrap PHPUnit pour que les avertissements deviennent visibles, voire bloquants via un gestionnaire d'erreurs personnalisé.
- Documenter dans le `README` du paquet quelles clés sont obligatoires et lesquelles ont un sens métier optionnel.
- Ajouter un test qui construit explicitement une organisation minimale, sans aucun override, pour garantir que les valeurs par défaut restent cohérentes dans le temps.

> Une factory partagée entre projets n'est pas un détail d'infrastructure de test : c'est une API interne, et elle mérite le même soin qu'une API publique, avec des contrats explicites plutôt que des hypothèses tacites.

## En résumé

Un `Undefined array key` dans une factory de tests n'est presque jamais un problème isolé : c'est le symptôme d'hypothèses non écrites, qui tiennent tant que personne ne s'en écarte. Rendre ces hypothèses explicites, avec des valeurs par défaut assumées et des erreurs levées volontairement, transforme un piège récurrent en garde-fou qui profite à tous les projets qui réutilisent la factory.
