Как проверить функцию, которая выводится случайным образом с помощью Jest?

Как проверить функцию, которая выводится случайным образом с помощью Jest? Вот так:

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

Таким образом, вывод functionToTest('Some predictable value') будет примерно таким:

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

Ответ 1

Вот что я поставил в верхней части тестового файла:

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

В тестах, запускаемых из этого файла, Math.random всегда возвращает 0.5.

Полный кредит должен пойти на это по идее: fooobar.com/questions/314364/..., в котором уточняется, что эта перезапись специфична для тестирования. Мой Object.create - это всего лишь дополнительная дополнительная осторожность, позволяющая избежать вмешательства со стороны самого Math.

Ответ 2

Я взял решение Стюарта Ватта и побежал с ним (и немного увлекся). Решение Стюарта хорошо, но меня не вдохновила мысль, что генератор случайных чисел всегда будет выпадать 0,5 - кажется, что бывали ситуации, когда вы рассчитываете на некоторую дисперсию. Я также хотел посмеяться над crypto.randomBytes для моих солей паролей (используя Jest на стороне сервера). Я потратил немного времени на это, поэтому я решил поделиться своими знаниями.

Одна из вещей, которые я заметил, заключается в том, что даже если у вас есть повторяющийся поток номеров, введение нового вызова в Math.random() может испортить все последующие вызовы. Я нашел способ обойти эту проблему. Этот подход должен быть применим ко всем случайным вещам, которые вам нужно высмеивать.

(примечание: если вы хотите украсть это, вам нужно установить Chance.js - yarn/npm add/install chance)

Чтобы подделать Math.random, поместите это в один из файлов, на которые указывает ваш массив package.json {"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

Вы заметите, что у Math.random() теперь есть параметр - начальное число. Это семя может быть строкой. Это означает, что когда вы пишете свой код, вы можете вызывать генератор случайных чисел, который вам нужен, по имени. Когда я добавил тест в код, чтобы проверить, сработало ли это, я не положил его в начало. Это испортило мои ранее издевательские снимки Math.random(). Но затем, когда я изменил его на Math.random('mathTest'), он создал новый генератор с именем "mathTest" и перестал перехватывать последовательность из последовательности по умолчанию.

Я также высмеял crypto.randomBytes для моих солей пароля. Поэтому, когда я пишу код для генерации своих солей, я могу написать crypto.randomBytes(32, 'user sign up salt').toString('base64'). Таким образом, я могу быть уверен, что ни один последующий вызов crypto.randomBytes не помешает моей последовательности.

Если кто-то еще заинтересован в том, чтобы издеваться над crypto, вот как. Поместите этот код внутри <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

А затем просто позвоните jest.mock('crypto') (опять же, он у меня есть в одном из моих "setupFiles"). Так как я выпускаю его, я пошел вперед и сделал его совместимым с методом обратного вызова (хотя я не собираюсь использовать его таким образом).

Эти два фрагмента кода проходят все 17 этих тестов (я создал функции __clearChances__ для beforeEach() - он просто удаляет все ключи из хэша chances)

Обновление: я использовал это в течение нескольких дней, и я думаю, что это работает довольно хорошо. Единственное, я думаю, что, возможно, лучшей стратегией было бы создание функции Math.useSeed, которая выполняется в верхней части тестов, требующих Math.random

Ответ 3

Я задал себе следующие вопросы:

  • Нужно ли мне проверять случайный выход? Если мне нужно, я бы, скорее всего, проверил диапазоны или удостоверился, что получил число в допустимом формате, а не само значение
  • Достаточно ли проверить значение для c?

Иногда есть способ инкапсулировать генерацию случайного значения в Mock и переопределить генерацию в вашем тесте, чтобы вернуть только известные значения. Это обычная практика в моем коде. Как издеваться над конструктором, похожим на новую Date(), похоже на аналогичный подход в jestjs.

Ответ 4

Вы всегда можете использовать jest-mock-random

Но он предлагает немного больше функциональности, чем насмешка, как было предложено в первом ответе.

Например, вы можете использовать до того, как testmockRandomWith(0.6); и ваш Math.random в тесте всегда будут возвращать это предсказуемое значение

Ответ 5

Я использовал:

beforeEach(() => {
    jest.spyOn(global.Math, 'random').mockReturnValue(0.123456789);
});

afterEach(() => {
    global.Math.random.mockRestore();
})

Легко добавлять и восстанавливать функциональность вне тестов.

Ответ 6

Насмешливые буквальные случайные данные не совсем способ проверить. Вопрос, как было сказано, является немного более широким, потому что "как проверить функцию, вывод которой является случайным", требует от вас статистического анализа вывода, чтобы обеспечить эффективную случайность - что, вероятно, было сделано создателями вашей псевдо- генератор случайных чисел.

Вместо этого подразумевается, что "вывод является случайным" означает, что вы хотите обеспечить правильное функционирование функции независимо от случайных данных, тогда достаточно просто посмеяться над вызовом Math.random, чтобы получить числа, отвечающие вашим конкретным критериям (охватывающим любую дисперсию). Эта функция является сторонней границей, которая, несмотря на необходимость тестирования, не является тем, что тестируется на основе моего вывода. Если это не так - в этом случае обратитесь к пункту выше.