Aller au contenu
Toutes les notes

3 min de lecture

Des données de test qui disent la vérité

Les fixtures mentent par omission. mongoose-test-factory lit votre schéma et génère des données qui le respectent. Comment il décide quoi générer, et les trois modes que j'utilise chaque jour.

Chaque projet Mongoose que j'ai rejoint avait un dossier fixtures/, et chacun de ces dossiers était discrètement faux. Un champ ajouté au schéma mais pas au JSON. Un enum qui gagnait une valeur que personne ne testait. Un required qui apparaissait et la moitié des fixtures ne s'enregistraient plus, alors quelqu'un marquait le test skip et passait à autre chose.

Les fixtures sont un instantané de ce à quoi le schéma ressemblait le jour où elles ont été écrites. Le schéma, c'est ce que le code croit aujourd'hui. mongoose-test-factory existe pour fermer cet écart : il lit le schéma et génère des données valides par rapport à lui, maintenant.

#Partir du schéma

Le paquet est un plugin. Appliquez-le, enveloppez le modèle, et le modèle obtient une factory().

user.model.tstypescript
import mongoose, { Schema } from 'mongoose'
import mongooseTestFactory, { withFactory } from 'mongoose-test-factory'

const userSchema = new Schema({
  name: { type: String, required: true },
  email: { type: String, required: true, unique: true },
  age: { type: Number, min: 18, max: 120 },
  isActive: { type: Boolean, default: true },
})

userSchema.plugin(mongooseTestFactory)
export const User = withFactory(mongoose.model('User', userSchema))
typescript
const user = User.factory().build()
// { name: 'John Doe', email: 'john.doe@example.com', age: 28, isActive: true }

Rien n'a été configuré. Le plugin a parcouru le schéma, vu une chaîne obligatoire nommée name, une chaîne unique nommée email, un nombre borné entre 18 et 120, et a généré en conséquence. Changez le schéma et la prochaine exécution change avec lui.

#Comment il décide quoi générer

Trois couches, par ordre de priorité.

  1. Un factoryType explicite sur le champ l'emporte. Une quarantaine de types sont intégrés : email, phone, price, slug, uuid, birthdate, tags, etc.
  2. Le nom du champ. userEmail, contactEmail et email ressemblent tous à des e-mails. price, cost et amount ressemblent à de l'argent. createdAt ressemble à un horodatage.
  3. Le type et ses validateurs. Un Number avec min et max reste dans les bornes. Une String avec un enum pioche dedans. required est toujours respecté.

Quand le nom est ambigu, dites-le dans le schéma :

typescript
const productSchema = new Schema({
  name: { type: String, factoryType: 'title' },
  vendor: { type: String, factoryType: 'company' },
  price: { type: Number, factoryType: 'price' },
  isActive: { type: Boolean, factoryType: 'active' }, // true environ 80 % du temps
  website: { type: String, factoryType: 'url' },
})

#Trois modes, trois sortes de tests

MéthodeRetourneTouche la baseÀ utiliser pour
build()objets simplesnontests unitaires, corps de requête
make()instances Mongoosenonvirtuels, méthodes, hooks
create()documents enregistrésouitests d'intégration
typescript
const body = User.factory().build() // rapide, aucune connexion requise
const doc = User.factory().make() // une instance, non persistée
const saved = await User.factory(50).create() // cinquante vraies lignes

La plupart de mes tests unitaires n'ouvrent jamais de connexion. build() est assez rapide pour que j'aie complètement arrêté de mettre en cache les données de test.

#Surcharges et relations

La valeur générée est un point de départ. Ce qui compte vraiment pour le test, fixez-le explicitement.

typescript
const admin = User.factory().with({ name: 'Admin', role: 'admin' }).build()

const author = await User.factory().create()
const posts = await Post.factory(5).with({ author: author._id }).create()

Ce second exemple est le motif que j'utilise le plus : un vrai parent, N enfants générés qui pointent vers lui. La factory remplit tout ce que je n'ai pas mentionné, et les assertions se lisent comme une intention plutôt qu'une mise en place.

#Ce qu'il ne fera pas

Il n'inventera pas vos règles métier. Si le total d'une commande doit égaler la somme de ses lignes, le schéma ne le dit pas, et la factory ne peut pas le savoir. Écrivez cela comme une surcharge, ou comme un petit utilitaire dans votre configuration de test qui construit des commandes cohérentes. Des données générées doivent être valides par rapport au schéma et honnêtes sur le reste ; prétendre comprendre le domaine serait un autre genre de mensonge.

Ça vous a parlé ?

Conversation

Vous construisez quelque chose de ce genre ?

Dites-moi sur quoi vous travaillez. Je réponds sous un jour.

M'écrire