web-dev-qa-db-fra.com

Comment tester une fonction dont la sortie est aléatoire en utilisant Jest?

Comment tester une fonction dont la sortie est aléatoire en utilisant Jest? Comme ça:

import cuid from 'cuid';  
const functionToTest = (value) => ({
    [cuid()]: {
        a: Math.random(),
        b: new Date().toString(),
        c: value,
    }
});

Donc, la sortie de functionToTest('Some predictable value') sera quelque chose comme:

{
  'cixrchnp60000vhidc9qvd10p': {
    a: 0.08715126430943698,
    b: 'Tue Jan 10 2017 15:20:58 GMT+0200 (EET)',
    c: 'Some predictable value'
  },
}
10
Bogdan Slovyagin

Voici ce que j'ai mis en haut de mon fichier de test:

const mockMath = Object.create(global.Math);
mockMath.random = () => 0.5;
global.Math = mockMath;

Dans les tests exécutés à partir de ce fichier, Math.random renvoie toujours 0.5

Le mérite devrait aller à l’idée: https://stackoverflow.com/a/40460395/2140998 , ce qui précise que cet écrasement est spécifique au test. Mon Object.create est juste un peu plus ma prudence supplémentaire évitant de toucher aux éléments internes de Math lui-même.

24
Stuart Watt

J'ai pris la solution de Stuart Watt et je l'ai utilisée (et je me suis un peu emportée). La solution de Stuart est bonne, mais l'idée de faire en sorte qu'un générateur de nombres aléatoires crisse toujours à 0,5 m'a semblé démesurée - il semblerait qu'il y ait des situations dans lesquelles vous comptez sur certains écarts. Je voulais aussi simuler crypto.randomBytes pour mon mot de passe sels (en utilisant Jest server-side). J'ai passé un peu de temps là-dessus et j'ai donc décidé de partager les connaissances. 

L’une des choses que j’ai remarquée est que, même si vous avez un flux de numéros répétable, introduire un nouvel appel dans Math.random() risque de gâcher tous les appels suivants. J'ai trouvé un moyen de contourner ce problème. Cette approche devrait être applicable à pratiquement toutes les choses aléatoires dont vous avez besoin de vous moquer.

(note latérale: si vous voulez voler ceci, vous devrez installer Chance.js - yarn/npm add/install chance)

Pour simuler Math.random, placez-le dans l'un des fichiers pointés par le tableau package.json de votre {"jest":{"setupFiles"}:

const Chance = require('chance')

const chances = {}

const mockMath = Object.create(Math)
mockMath.random = (seed = 42) => {
  chances[seed] = chances[seed] || new Chance(seed)
  const chance = chances[seed]
  return chance.random()
}

global.Math = mockMath

Vous remarquerez que Math.random() a maintenant un paramètre - une graine. Cette graine peut être une chaîne. Cela signifie que, pendant que vous écrivez votre code, vous pouvez appeler par le nom du générateur de nombres aléatoires de votre choix. Lorsque j'ai ajouté un test au code pour vérifier si cela fonctionnait, je ne l'ai pas mis en germe. Il a bousillé mes instantanés Math.random() précédemment fictifs. Mais ensuite, lorsque je l’ai changée en Math.random('mathTest'), il a créé un nouveau générateur appelé "mathTest" et a cessé d’intercepter la séquence par défaut.

Je me suis aussi moqué de crypto.randomBytes pour mon mot de passe sels. Ainsi, lorsque j'écris le code pour générer mes sels, je peux écrire crypto.randomBytes(32, 'user sign up salt').toString('base64'). De cette façon, je peux être à peu près sûr qu'aucun appel ultérieur à crypto.randomBytes ne va gâcher ma séquence.

Si quelqu'un d'autre souhaite se moquer de crypto de cette manière, voici comment procéder. Mettez ce code dans <rootDir>/__mocks__/crypto.js:

const crypto = require.requireActual('crypto')
const Chance = require('chance')

const chances = {}

const mockCrypto = Object.create(crypto)
mockCrypto.randomBytes = (size, seed = 42, callback) => {
  if (typeof seed === 'function') {
    callback = seed
    seed = 42
  }

  chances[seed] = chances[seed] || new Chance(seed)
  const chance = chances[seed]

  const randomByteArray = chance.n(chance.natural, size, { max: 255 })
  const buffer = Buffer.from(randomByteArray)

  if (typeof callback === 'function') {
    callback(null, buffer)
  }
  return buffer
}

module.exports = mockCrypto

Et ensuite, appelez simplement jest.mock('crypto') (là encore, je l’ai dans un de mes "setupFiles"). Depuis que je le publie, je suis allé de l'avant et je l'ai rendu compatible avec la méthode de rappel (bien que je n'ai pas l'intention de l'utiliser de cette façon).

Ces deux morceaux de code passent les 17 de ces tests (J'ai créé des fonctions __clearChances__ pour la beforeEach()s - il supprime simplement toutes les clés du hachage chances)

Mise à jour: Je l'utilise depuis quelques jours maintenant et je pense que cela fonctionne plutôt bien. La seule chose à faire est de penser qu'une meilleure stratégie consisterait peut-être à créer une fonction Math.useSeed exécutée au sommet des tests nécessitant Math.random

4
HasFiveVowels

Je me pose les questions suivantes:

  • Dois-je vraiment tester la sortie aléatoire? Si nécessaire, je testerais probablement les plages ou m'assurerais de recevoir un nombre dans un format valide, et non la valeur elle-même.
  • Est-il suffisant de tester la valeur pour c?

Il existe parfois un moyen d'encapsuler la génération de la valeur aléatoire dans un Mock et de remplacer la génération de votre test pour renvoyer uniquement les valeurs connues. Ceci est une pratique courante dans mon code. Comment se moquer d'un constructeur comme new Date () ressemble à une approche similaire dans jestjs .

3
cringe

Vous pouvez toujours utiliser jest-mock-random

Mais il offre un peu plus de fonctionnalités que de s'en moquer, comme proposé dans la première réponse.

Par exemple, vous pourriez utiliser avant testmockRandomWith(0.6); et votre Math.random dans le test renverra toujours cette valeur prévisible

0
SirPeople

Se moquer de données aléatoires littérales n'est pas exactement le moyen de tester. Comme il est dit, la question est un peu large, car "comment tester une fonction dont la sortie est aléatoire" exige que vous effectuiez une analyse statistique de la sortie pour garantir un caractère aléatoire efficace - ce qui a probablement été fait par les créateurs de votre pseudo-descriptif. générateur de nombres aléatoires.

Déduire à la place "cette sortie est aléatoire" signifie que vous voulez vous assurer que la fonction fonctionne correctement quelles que soient les données aléatoires. Il suffit alors de se moquer de l'appel Math.random pour renvoyer les numéros qui répondent à vos critères spécifiques (couvrant toute variance). Cette fonction est une limite de tierce partie qui, même si elle a besoin d'être testée, n'est pas ce qui est testé en fonction de mon inférence. Sauf si c'est le cas - dans ce cas, reportez-vous au paragraphe ci-dessus. 

0
Richard Barker