Kodėl Jest tapo tokiu populiariu pasirinkimu
Kai prieš kelerius metus pradėjau rimčiau domėtis JavaScript testavimu, rinkoje buvo tikras chaosas. Mocha, Jasmine, Karma, QUnit – galvą suk nuo pasirinkimų gausos. Kiekvienas framework’as reikalavo savų konfigūracijų, papildomų bibliotekų assertion’ams, mock’ų sistemų. Tada atsirado Jest, ir viskas pasikeitė.
Facebook komanda sukūrė Jest kaip atsaką į realias problemas, su kuriomis susiduria kūrėjai kasdien. Jie norėjo framework’o, kuris veiktų iš dėžės be valandų trunkančių konfigūracijų. Ir jiems pavyko – Jest ateina su assertion biblioteka, mock’ų sistema, code coverage įrankiais ir net snapshot testavimu. Viskas vienoje vietoje.
Kas iš tikrųjų padarė Jest tokį populiarų, tai ne tik funkcionalumas. Tai greitis ir paprastumas. Zero-config filosofija reiškia, kad daugeliu atvejų galite tiesiog įdiegti Jest ir pradėti rašyti testus. Nereikia kelių dienų konfigūruoti webpack’o, babel’io ir kitų įrankių tarpusavio sąveiką.
Kaip pradėti su Jest be galvos skausmo
Įdiegti Jest yra juokingai paprasta. Jei dirbate su npm, tiesiog paleiskite:
npm install --save-dev jest
Arba su yarn:
yarn add --dev jest
Dabar package.json faile pridėkite test script’ą:
"scripts": {
"test": "jest"
}
Ir viskas. Rimtai. Dabar galite kurti failus su .test.js arba .spec.js pabaigomis, ir Jest juos automatiškai ras bei paleisis.
Paprastas pavyzdys atrodytų taip. Tarkime, turite funkciją sum.js:
function sum(a, b) {
return a + b;
}
module.exports = sum;
Jūsų testas sum.test.js būtų:
const sum = require('./sum');
test('sudeda 1 + 2 ir gauna 3', () => {
expect(sum(1, 2)).toBe(3);
});
Paleiskite npm test ir matote žalią varnelę. Jei testas nepraeitų, gautumėte labai aiškų pranešimą, kur ir kas nepavyko.
Matchers sistema ir kaip ja naudotis protingai
Jest matcher’iai – tai komandos, kuriomis sakote, ko tikitės iš savo kodo. Pradedantieji dažnai naudoja tik toBe() ir toEqual(), bet Jest turi dešimtis įvairių matcher’ių, kurie daro testus skaitomesnius ir tikslesnius.
toBe() naudoja Object.is() lygybei patikrinti – tai reiškia, kad jis tikrina griežtą lygybę. Objektams ir masyvams geriau naudoti toEqual(), kuris rekursyviai tikrina visas savybes:
test('objektų lyginimas', () => {
const data = {one: 1};
data['two'] = 2;
expect(data).toEqual({one: 1, two: 2});
});
Yra ir daugiau naudingų matcher’ių. toBeNull(), toBeUndefined(), toBeDefined(), toBeTruthy(), toBeFalsy() – visi jie daro tiksliai tai, ką sako. Skaičiams turime toBeGreaterThan(), toBeLessThan(), toBeCloseTo() (pastarasis naudingas float skaičiams).
String’ams galite naudoti regex’us su toMatch():
test('nėra I žodžje team', () => {
expect('team').not.toMatch(/I/);
});
Masyvams ir iterable objektams yra toContain():
test('pirkinių sąraše yra pienas', () => {
const shoppingList = ['pienas', 'duona', 'kiaušiniai'];
expect(shoppingList).toContain('pienas');
});
Asinchroninio kodo testavimas – kur dauguma suklysta
Čia prasideda tikrasis smagumas. Asinchroninis kodas JavaScript’e yra visur, ir jį testuoti reikia mokėti. Jest palaiko kelis būdus, ir svarbu suprasti, kada kurį naudoti.
Pirmasis būdas – callback’ai. Jei jūsų funkcija naudoja callback’us, perduokite done parametrą į test funkciją:
test('duomenys yra peanut butter', done => {
function callback(data) {
try {
expect(data).toBe('peanut butter');
done();
} catch (error) {
done(error);
}
}
fetchData(callback);
});
Bet atvirai – callback’ai yra 2010-ųjų stilius. Šiais laikais naudojame Promise’us ir async/await. Su Promise’ais tiesiog grąžinkite promise iš testo:
test('duomenys yra peanut butter', () => {
return fetchData().then(data => {
expect(data).toBe('peanut butter');
});
});
Svarbu: jei negrąžinsite promise, testas baigsis prieš promise resolve’indamasis, ir jūsų assertion’ai niekada nebus patikrinti.
Dar geriau – naudokite async/await. Tai skaitomiausia ir švariausia sintaksė:
test('duomenys yra peanut butter', async () => {
const data = await fetchData();
expect(data).toBe('peanut butter');
});
Jei tikitės, kad promise bus rejected, naudokite .rejects:
test('fetch nepavyksta su klaida', async () => {
await expect(fetchData()).rejects.toThrow('error');
});
Mock’ai ir spy’ai – kaip testuoti nepriklausomai
Vienas didžiausių Jest privalumų yra integruota mock’ų sistema. Nereikia jokių papildomų bibliotekų kaip Sinon.js. Viskas jau yra.
Mock funkcijos leidžia jums pakeisti tikrąsias funkcijas ir stebėti, kaip jos kviečiamos. Paprasčiausias būdas sukurti mock funkciją:
const mockCallback = jest.fn(x => 42 + x);
test('mock funkcija', () => {
mockCallback(0);
mockCallback(1);
expect(mockCallback).toHaveBeenCalledTimes(2);
expect(mockCallback).toHaveBeenCalledWith(1);
expect(mockCallback).toHaveBeenLastCalledWith(1);
});
Realybėje dažnai reikia mock’inti išorinius modulius. Tarkime, turite modulį, kuris daro API kvietimus. Testuodami nenorite tikrai kviesti API. Jest leidžia lengvai mock’inti visą modulį:
jest.mock('./api');
const api = require('./api');
test('vartotojas gaunamas iš API', async () => {
api.getUser.mockResolvedValue({name: 'Jonas', age: 30});
const user = await getUserData(1);
expect(user.name).toBe('Jonas');
});
mockResolvedValue() yra super patogus būdas mock’inti async funkcijas, kurios grąžina Promise. Yra ir mockRejectedValue() klaidų atvejams.
Kartais reikia mock’inti tik dalį modulio, palikiant likusią dalį tikrą. Naudokite requireActual:
jest.mock('./utils', () => ({
...jest.requireActual('./utils'),
generateId: jest.fn(() => 'test-id-123')
}));
Snapshot testavimas – ar tai stebuklinga kulka ar pinkles
Snapshot testavimas yra viena iš kontroversišiausių Jest funkcijų. Vieni žmonės jį myli, kiti nekenčia. Aš pats esu kažkur per vidurį.
Idėja paprasta: vietoj to, kad rašytumėte dešimtis assertion’ų, Jest išsaugo jūsų komponento išvestį į failą ir palygina ją kiekvieną kartą. Tai ypač populiaru React komponentų testavimui:
import renderer from 'react-test-renderer';
import Button from './Button';
test('Button komponentas', () => {
const tree = renderer.create().toJSON();
expect(tree).toMatchSnapshot();
});
Pirmą kartą paleidus testą, Jest sukuria snapshot failą. Kiekvieną kitą kartą jis lygina su tuo snapshot’u. Jei kas nors pasikeičia, testas nepraeis.
Problema su snapshot’ais – jie gali skatinti tingumą. Per lengva tiesiog paspausti „u” (update snapshots) nematant, kas iš tikrųjų pasikeitė. Tada snapshot’ai tampa bevertėmis – jie tik seka kodo pokyčius, bet netikrina, ar tie pokyčiai teisingi.
Mano patarimas: naudokite snapshot’us mažiems, stabilių komponentams. Jei jūsų snapshot failas yra 500 eilučių, kažkas negerai. Geriau rašykite tikslius assertion’us svarbiems dalykams ir snapshot’us naudokite kaip papildomą apsaugą nuo atsitiktinių pokyčių.
Taip pat galite naudoti inline snapshot’us, kurie išsaugomi tiesiog teste:
test('inline snapshot', () => {
expect({name: 'Jonas', age: 30}).toMatchInlineSnapshot(`
Object {
"age": 30,
"name": "Jonas",
}
`);
});
Setup ir teardown – kaip organizuoti testų aplinką
Kai testų skaičius auga, pastebite, kad kartojate tą patį setup kodą. Jest turi keturias pagrindines funkcijas, kurios padeda organizuoti testų aplinką: beforeEach, afterEach, beforeAll, afterAll.
beforeEach(() => {
initializeDatabase();
});
afterEach(() => {
clearDatabase();
});
test('vartotojas gali būti sukurtas', () => {
// testas čia
});
test('vartotojas gali būti ištrintas', () => {
// testas čia
});
beforeEach ir afterEach vykdomi prieš ir po kiekvieno testo. beforeAll ir afterAll vykdomi vieną kartą prieš visus testus ir po visų testų.
Galite grupuoti testus su describe blokai, ir kiekvienas blokas gali turėti savo setup/teardown:
describe('Vartotojų valdymas', () => {
beforeEach(() => {
// setup tik šiems testams
});
test('vartotojas sukuriamas', () => {
// testas
});
describe('Vartotojo trynimas', () => {
beforeEach(() => {
// papildomas setup tik trynimo testams
});
test('vartotojas ištrinamas', () => {
// testas
});
});
});
Svarbu suprasti vykdymo tvarką. Jest vykdo visus describe callback’us prieš vykdydamas bet kokius testus. Tai reiškia, kad setup kodas turėtų būti before* funkcijose, ne tiesiog describe bloke.
Kas lieka už kadro ir kaip su tuo gyventi
Jest nėra tobulas. Yra dalykų, kurie gali erzinti, ypač dirbant su sudėtingesnėmis aplikacijomis.
Greitis gali tapti problema dideliuose projektuose. Jest pagal nutylėjimą paleidžia testus paraleliai, kas yra gerai, bet kartais tai sukelia sunkiai aptinkamas problemas, ypač jei testai dalijasi būsena. Galite išjungti paralelizmą su --runInBand, bet tada testai lėtėja.
Konfigūracija, nors ir minimali, kartais gali būti painoka. Ypač dirbant su TypeScript, absoliučiais keliais, arba specifinėmis webpack konfigūracijomis. Tuomet jest.config.js failas gali išsipūsti.
ESM (ES Modules) palaikymas vis dar eksperimentinis. Jei jūsų projektas naudoja native ES modulius, gali tekti pasikankinti su konfigūracijomis arba naudoti --experimental-vm-modules flag’ą.
Bet šios problemos yra menkniekiai palyginus su tuo, ką Jest duoda. Didžioji dalis projektų dirba puikiai su minimalia arba jokia konfigūracija. Ir kai kyla problemų, Jest dokumentacija ir bendruomenė yra gana aktyvios.
Praktinis patarimas: pradėkite su paprasčiausia konfigūracija. Pridėkite sudėtingumo tik tada, kai tikrai reikia. Per daug kūrėjų iš karto bando sukonfigūruoti viską idealiai ir paskui stebisi, kodėl nieko neveikia.
Dar vienas dalykas – code coverage. Jest turi integruotą Istanbul coverage įrankį. Paleiskite testus su --coverage flag’u ir gausite detalų raportą, kurios kodo dalys yra padengtos testais. Bet nepasiduokite 100% coverage kulto. Geriau turėti 70% coverage su prasmingais testais nei 100% su tuščiais testais, kurie nieko netikrina.
Galiausiai, mokykitės iš kitų. Pažiūrėkite, kaip populiarūs open-source projektai naudoja Jest. React, Vue, Babel – visi jie naudoja Jest ir jų test suite’ai yra puikūs mokymosi šaltiniai. Matote realius pavyzdžius, kaip testuoti sudėtingus scenarijus, kaip organizuoti testus, kaip naudoti pažangias Jest funkcijas.
Jest padarė JavaScript testavimą prieinamą ir malonų. Tai ne mažas pasiekimas ekosistemoje, kuri garsėja sudėtingumu ir įrankių gausa. Jei dar nenaudojate Jest, išbandykite kitame projekte. O jei jau naudojate, tikrai yra funkcijų, kurias dar neatradote ir kurios gali padaryti jūsų testus geresnius.
