2017-01-10 15 views
5

Jak przetestować funkcję, której wynik jest losowy przy użyciu Jest? Tak:Jak przetestować funkcję, której wynik jest losowy przy użyciu Jest?

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

Więc wyjście functionToTest('Some predictable value') będzie coś takiego:

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

Co, twoim zdaniem, "test ... ten wynik jest losowy", nawet znaczy? Odwróć monetę i możesz dostać głowy dwa razy z rzędu; jeśli nie jest to możliwe, nie jest przypadkowe. Punkt "losowy" to * nie wiesz, jakiej wartości szukasz *. W praktyce można uruchomić tę funkcję dwukrotnie dla tego samego wejścia i oczekiwać różnych wyników, ale niektóre (bardzo niskie) procent czasu, który powinien zawodzić, nawet z działającym kodem. –

+0

Pewnie, losowość testu jest nonsensem. Pytam, jak ** przetestować ** tego rodzaju przypadki w zasadzie. Po prostu nie mam pojęcia, jak to zrobić. –

+0

Klucz jest tutaj testem, a nie przypadkowością. Może losowa część musi zostać pominięta. Jestem noobem podczas testowania, więc poproszę o noob. –

Odpowiedz

12

Oto co umieścić w górnej części mojego pliku testowego:

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

W testach uruchomić z tego pliku, Math.random zawsze zwraca 0.5.

Pełne uznanie zasługuje na tę ideę: https://stackoverflow.com/a/40460395/2140998, która wyjaśnia, że ​​to nadpisanie jest specyficzne dla testu. Mój Object.create jest tylko moim dodatkowym dodatkiem trochę ostrożności, unikając ingerencji w wnętrza samego Math.

+0

słodki! Chciałbym, żeby to było w dokumentach. – looshi

3

bym zadać sobie następujące pytania:

  • Czy naprawdę trzeba przetestować wyjście losowej ? Jeśli muszę, najprawdopodobniej testowałem zakresy lub upewniłem się, że otrzymałem numer w ważnym formacie, a nie samą wartość.
  • Czy wystarczy przetestować wartość dla c?

Czasami istnieje sposób do hermetyzacji generowanie wartości losowej w Mock i zastąpić pokolenie w próbie powrotu tylko znane wartości. Jest to powszechna praktyka w moim kodzie. How to mock a constructor like new Date() brzmi jak podobne podejście w jestjs.

+0

Świetnie, właśnie to chciałem wiedzieć. Nie tylko bezpośrednia odpowiedź, ale coś, co kryje się za testowaniem. Dzięki! –

1

Wziąłem rozwiązanie Stuarta Wata i uciekłem z nim (i trochę mnie porwał). Rozwiązanie Stuarta jest dobre, ale byłem rozczarowany tym, że generator liczb losowych zawsze wypluwa 0.5 - wydaje się, że byłyby sytuacje, w których liczysz na jakąś wariancję. Chciałem również wyśmiewać się z crypto.randomBytes dla moich soli do haseł (używając polecenia "po stronie serwera"). Spędziłem trochę czasu, więc pomyślałem, że podzielę się wiedzą.

Jedną z rzeczy, które zauważyłem jest to, że nawet jeśli masz powtarzalny strumień liczb, wprowadzenie nowego połączenia pod numer Math.random() może zepsuć wszystkie kolejne połączenia. Znalazłem sposób obejścia tego problemu. To podejście powinno mieć zastosowanie do praktycznie każdej przypadkowej rzeczy, którą trzeba udawać.

(dygresja: jeśli chcesz ukraść to, musisz zainstalować Chance.js - yarn/npm add/install chance)

drwić Math.random, umieścić to w jednym z plików wskazał przez swoich package.json „s {"jest":{"setupFiles"} tablicy:

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 

Zauważysz, że Math.random() ma teraz parametr - nasiono. Ten materiał siewny może być ciągiem. Oznacza to, że podczas pisania kodu można wywoływać generator liczb losowych według nazwy. Kiedy dodałem test kodu, aby sprawdzić, czy to zadziałało, nie umieściłem go. Zepsuł on moje wcześniej sfilmowane migawki Math.random(). Ale kiedy zmieniłem go na Math.random('mathTest'), utworzył nowy generator o nazwie "mathTest" i przestał przechwytywać sekwencję z domyślnej.

Ja także wyśmiałem crypto.randomBytes za moje sole do haseł. Kiedy piszę kod generujący moje sole, mogę napisać: crypto.randomBytes(32, 'user sign up salt').toString('base64'). W ten sposób mogę być pewny, że żadne kolejne wywołanie do crypto.randomBytes nie zepsuje się z moją sekwencją.

Jeśli ktoś jeszcze jest zainteresowany kpiną z crypto w ten sposób, oto jak. Umieść ten kod wewnątrz <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 

A potem po prostu zadzwonić jest.mock('crypto') (znowu, mam go w jednym z moich „setupFiles”). Odkąd go wypuszczam, robiłem postępy zgodnie z metodą wywołania zwrotnego (chociaż nie mam zamiaru używać go w ten sposób).

Te dwa fragmenty kodu przejść wszystkie 17 these tests (stworzyłem __clearChances__ funkcje dla beforeEach() s - to po prostu usuwa wszystkie klucze od chances hash)

Aktualizacja: używa tego na kilka dni i teraz Myślę, że działa całkiem nieźle. Jedyną rzeczą jest to, myślę, że być może lepszą strategią byłoby stworzenie Math.useSeed funkcji, który jest prowadzony w górnej części testów, które wymagają Math.random

0

Zawsze możesz użyć jest-mock-random

ale oferuje nieco więcej funkcji niż wyśmianie tak jak zaproponowano w pierwszej odpowiedzi:

Powiązane problemy