IlmHamroh
JavaScript Full-stack/22-qism. Frontend testlash16/16-dars20 daqiqa
Mundarija (31)

Jest va eski test muhitlari: Jest'li loyihani o'qish va Vitest'ga ko'chirish

Qisqacha: Jest — Vitest'dan oldingi eng mashhur JavaScript test vositasi; ko'p mavjud loyihalar hali unda. API deyarli bir xil: describe, test, expect, jest.fn ↔ vi.fn. Farq — muhitda: Jest kodni o'zi o'giradi (babel-jest, ts-jest, @swc/jest), sukut bo'yicha CommonJS'da ishlaydi va ES modullar uchun hali --experimental-vm-modules bayrog'ini so'raydi. CRA loyihalari Jest + jsdom bilan keladi, Enzyme esa eskirgan. Ko'chirishda kod deyarli o'zgarmaydi, lekin tuzoqlar bor: done callback, mockReset, jest.requireActual.

Bu darsda

  • Jest'li loyihaning jest.config.js va test fayllarini o'qiysiz.
  • babel-jest, ts-jest va @swc/jest farqini, ESM muammosini tushuntirasiz.
  • CRA testlari va Enzyme kodini tanib, Enzyme testini RTL'ga qayta yozasiz.
  • Jest'dan Vitest'ga ko'chirish bosqichlari va jim tuzoqlarini bilasiz.
  • vazifalar dagi Jest tajribasidan (283/329) narxni raqamlar bilan baholaysiz.

Oldin bilishingiz kerak: Vitest I, Vitest II, RTL I, CommonJS va ESM.

1. Nega bu kerak?

Vitest I darsida va'da qilgan edik: "Jest — eng mashhur eski test vositasi, uni alohida ko'ramiz". Sizning birinchi ishingiz deyarli albatta yangi loyiha bo'lmaydi. Jasur aka «Bahor» uchun 2021 yilda yozilgan buyurtma panelini beradi: package.json da "test": "react-scripts test", papkada App.test.js, setupTests.js, ba'zi testlarda shallow(...). Bularni o'qiy olmasangiz — ishga kirisha olmaysiz.

Jest 2014 yilda Facebook'da yaratilgan, 2022 yildan OpenJS Foundation loyihasi. Vitest 2021 yilda paydo bo'ldi va API'ni ataylab Jest'ga o'xshatdi. Shuning uchun Jest testini o'qish — siz bilgan narsaning 90 %i. Qolgan 10 % — muhit: kod qanday o'giriladi, modullar qanday yuklanadi, eski kutubxonalar.

Bu darsda vazifalar o'zgarmaydi. Uning o'rniga kanon tajribasi: holat 15 ning nusxasi Jest 30 da ishga tushirildi. Natija — ko'chirish narxining aniq raqamlari.

2. Jest API: siz allaqachon bilasiz

2.1 Bir xil narsa, boshqa nom

Jest Vitest
global describe, test, expect import { … } from "vitest" (yoki globals: true)
jest.fn(), jest.spyOn() vi.fn(), vi.spyOn()
jest.mock("./sms") vi.mock("./sms")
jest.requireActual("./sms") await vi.importActual("./sms")
jest.useFakeTimers(), jest.advanceTimersByTime() vi.useFakeTimers(), vi.advanceTimersByTime()
jest.setTimeout(10000) vi.setConfig({ testTimeout: 10000 })
jest.config.js vitest.config.ts (yoki vite.config.ts dagi test)

Jest'da describe va expect import qilinmaydi — ular global. Shuning uchun eski test faylining boshida import yo'q bo'lsa, hayron bo'lmang.

Farqning kichikligini o'zingiz ko'ring. Pastdagi test Jest uslubida yozilgan — faqat jest nomini Vitest'ning vi siga ulaymiz:

jsx
import { describe, expect, test, vi as jest } from "vitest";

function createReminder(send) {
  return (order) =>
    setTimeout(() => send(order.phone, "Buyurtmangiz tayyor"), 15000);
}

describe("eslatma (Jest uslubida)", () => {
  test("15 soniyadan keyin bitta SMS", () => {
    jest.useFakeTimers();
    const send = jest.fn();
    createReminder(send)({ phone: "+998 90 000 00 12" });
    jest.advanceTimersByTime(14999);
    expect(send).not.toHaveBeenCalled();
    jest.advanceTimersByTime(1);
    expect(send).toHaveBeenCalledWith(
      "+998 90 000 00 12",
      "Buyurtmangiz tayyor",
    );
    jest.useRealTimers();
  });
});

Konsolda:

text
  ✓ eslatma (Jest uslubida) > 15 soniyadan keyin bitta SMS
Testlar: 1 o'tdi (jami 1)

Tana — o'zgarmagan Jest kodi. Kanon tajribasida ham aynan shu fokus ishlatildi, faqat teskari yo'nalishda: Vitest testlarini Jest'da ishga tushirish uchun vitest importini Jest'ga ulaydigan kichik shim (o'rinbosar modul) yozildi (izoh qatori telefon uchun bo'lingan):

js
// Tajriba (22/#16): testlar "vitest" dan import qiladi —
// Jest'da o'sha nomlar
import { jest, describe, test, it, expect, beforeEach, afterEach, beforeAll, afterAll } from "@jest/globals";
export { describe, test, it, expect, beforeEach, afterEach, beforeAll, afterAll };
export const vi = jest;

2.2 Jest terminali

Jest chiqishi biroz boshqacha. Biz Jest 30.5.2 ni alohida papkada ishga tushirdik (kanon emas): order.js buyurtmani tasdiqlaydi va sms.send ni chaqiradi, test SMS modulini jest.mock bilan soxtalaydi.

js
const { confirmOrder } = require("./order");
const sms = require("./sms");

jest.mock("./sms");

describe("confirmOrder", () => {
  test("SMS yuboriladi, holat — qabul", () => {
    const phone = "+998 90 000 00 12";
    const order = confirmOrder({ id: 12, phone });
    expect(order.status).toBe("qabul");
    expect(sms.send).toHaveBeenCalledWith(
      phone,
      "Buyurtma #12 qabul qilindi",
    );
  });
});
text
PASS src/order.test.js
  confirmOrder
    √ SMS yuboriladi, holat — qabul (3 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        0.319 s, estimated 1 s
Ran all test suites.

Uch narsaga qarang:

  • require — CommonJS (CommonJS va ESM dagi eski tizim). Jest sukut bo'yicha shunda ishlaydi.
  • jest.mock importlardan keyin yozilgan, lekin ishladi. babel-jest ning babel-plugin-jest-hoist plagini uni fayl boshiga ko'taradi — Vitest'dagi vi.mock hoisting'ining asli (Vitest II).
  • Test Suites — Jest'da test fayli "suite" deb ataladi; Vitest'da Test Files. Windows terminalida ✓ o'rniga √ chiqadi.

Yiqilgan testda Jest Expected: 3 / Received: 2 deb yozadi (Vitest — - Expected / + Received). Ma'nosi bir xil.

3. jest.config.js va kod o'giruvchilar

3.1 Asosiy maydonlar

Jest Vite'ni bilmaydi. Ikki vositada test fayli qanday yo'l bosib o'tishini solishtiring:

flowchart LR
  subgraph J["Jest"]
    J1["test.tsx"] --> J2["transformer<br/>babel-jest / ts-jest"]
    J2 --> J3["CommonJS<br/>(ESM — bayroq bilan)"]
    J3 --> J4["vm konteksti<br/>+ jsdom"]
  end
  subgraph V["Vitest"]
    V1["test.tsx"] --> V2["Vite<br/>plaginlar, alias"]
    V2 --> V3["ESM"]
    V3 --> V4["worker<br/>+ jsdom"]
  end

Rasmda: Vitest ilovani quradigan o'sha Vite'dan foydalanadi — plaginlar, taxalluslar, import.meta.env testda ham bor. Jest'da esa har biri uchun alohida sozlama kerak:

js
// jest.config.js — oddiy React loyihasi (CommonJS)
module.exports = {
  testEnvironment: "jsdom", // jest-environment-jsdom paketi alohida
  setupFilesAfterEnv: ["<rootDir>/src/setupTests.js"],
  testMatch: ["<rootDir>/src/**/*.test.{js,jsx}"],
  moduleNameMapper: {
    "\\.(css|less)$": "identity-obj-proxy", // CSS importlari
    "^@/(.*)$": "<rootDir>/src/$1", // taxallus
  },
  transform: { "^.+\\.[jt]sx?$": "babel-jest" },
  collectCoverageFrom: ["src/**/*.{js,jsx}"],
  coverageThreshold: { global: { lines: 60 } },
};
Maydon Vitest'dagi o'xshashi
testEnvironment environment
setupFilesAfterEnv setupFiles
testMatch include
moduleNameMapper resolve.alias (Vite'dan)
transform kerak emas — Vite o'giradi
coverageThreshold coverage.thresholds

Nomga e'tibor bering: setupFilesAfterEnv ("muhit tayyor bo'lgach"). Eski loyihalarda xato yozilgan setupFilesAfterEach ham uchraydi. Biz sinadik — Jest testlarni baribir ishga tushiradi, faqat ogohlantiradi: Unknown option "setupFilesAfterEach" with value ["./x.js"] was found. This is probably a typing mistake. ("noma'lum sozlama, ehtimol yozishda xato"). Sozlash fayli esa ulanmaydi va jest-dom matcherlari yo'q bo'lib qoladi.

moduleNameMapper dagi CSS qatori ham muhim. Komponent import "./card.css" qiladi. Jest uchun bu JavaScript fayl — u CSS'ni JS sifatida o'qib, sintaksis xatosi beradi. identity-obj-proxy yoki bo'sh modul — o'rinbosar.

3.2 Kim o'giradi: babel-jest, ts-jest, @swc/jest

Jest faqat JavaScript'ni tushunadi. JSX va TypeScript'ni transformer o'giradi:

Transformer Qanday Narxi
babel-jest sukut; Babel konfiguratsiyasi bo'yicha turlarni tekshirmaydi
ts-jest TypeScript kompilyatori bilan sekinroq, lekin tur xatosi testni yiqitadi
@swc/jest Rust'da yozilgan SWC eng tez; turlarni tekshirmaydi

ts-jest ning "turlarni ham tekshiradi" degan afzalligi Vite loyihasida kamroq kerak: turlarni baribir tsc alohida tekshiradi (Vitest I). Kanon tajribasida ts-jest 29.4.14 olindi: Jest 30 bilan ishlaydigan ESM preset'i bor. Narxi ko'rindi — komponent testlarining birinchi (sovuq) ishga tushirilishi 28.1 s.

4. ES modullar: Jest'ning eng og'riqli joyi

Zamonaviy loyihada "type": "module" va import. Jest esa ichida CommonJS'da ishlaydi. Biz sinab ko'rdik — transformersiz, sozlamasiz ESM test:

text
FAIL esm/price.test.js
  ● Test suite failed to run

    Must use import to load ES Module: C:\…\esm\price.test.js

    The file contains ESM syntax (import/export) that could not be executed as CommonJS. Either:
      - Configure a transform (e.g. babel-jest) that compiles this file to CommonJS (see https://jestjs.io/docs/code-transformation)

Tarjima: "ES modulni yuklash uchun import kerak. Faylda CommonJS sifatida bajarib bo'lmaydigan ESM sintaksisi bor". Ikki yo'l: kodni CommonJS'ga o'girish (Babel) yoki Node'ning eksperimental VM modullarini yoqish:

bash
node --experimental-vm-modules node_modules/jest/bin/jest.js

Shu bayroq bilan o'sha test o'tdi, lekin har ishga tushirishda ExperimentalWarning: VM Modules is an experimental feature. Ikkinchi muammo — soxta modul. ESM'da import ni ko'tarib bo'lmaydi, shuning uchun jest.mock ishlamaydi:

text
FAIL esm/order.test.js
  ● Test suite failed to run

    ReferenceError: require is not defined

ESM'dagi yo'l — jest.unstable_mockModule va dinamik import():

js
import { jest } from "@jest/globals";

jest.unstable_mockModule("./sms.js", () => ({ send: jest.fn() }));
const { send } = await import("./sms.js");
const { confirmOrder } = await import("./order.js");

Bu ishladi (PASS). Lekin nomidagi unstable — "beqaror API". Jest 30.5 hujjati ham (2026-10 holatiga) ESM qo'llovini "eksperimental" deydi: jest.mock faqat CommonJS fayllarga ta'sir qiladi, ESM uchun — jest.unstable_mockModule. Yangilik: Node 24.9+ da avtomatik mock va __mocks__ papkasidagi qo'lda yozilgan soxtalar ESM'ga ham qo'llanadi (top-level await li modullardan tashqari). Vitest'da esa vi.mock ESM'da odatdagidek ishlaydi: Vite importlarni o'zi boshqaradi.

Tekshirib ko'ring: Jest'li loyihada "type": "module" qo'shdingiz va hamma testlar yiqildi. Nima bo'ldi?

Javob

.js fayllar endi ES modul. Jest ularni CommonJS sifatida yuklashga urinadi va Must use import to load ES Module beradi. Yo "type": "module" ni qaytaring (Babel o'girishi bilan qoling), yo --experimental-vm-modules va jest.mock → jest.unstable_mockModule ga o'ting. Vite loyihasida bu — Vitest'ga o'tish uchun kuchli sabab.

5. CRA testlari va Enzyme

5.1 Create React App

Create React App (CRA, react-scripts) 2016–2025 yillarda React loyihasini boshlashning rasmiy yo'li edi. React jamoasi uni 2025-02-14 da rasman to'xtatdi ("Sunsetting Create React App", react.dev blogi). Oxirgi react-scripts — 5.0.1 (2022-04), ichida Jest 27. Lekin bunday loyihalar minglab. CRA'da test shunday ko'rinadi:

js
// src/setupTests.js — CRA har testdan oldin o'zi ulaydi
import "@testing-library/jest-dom";
jsx
// src/App.test.js — CRA shablonidagi birinchi test
import { render, screen } from "@testing-library/react";
import App from "./App";

test("renders learn react link", () => {
  render(<App />);
  const linkElement = screen.getByText(/learn react/i);
  expect(linkElement).toBeInTheDocument();
});

npm test → react-scripts test → Jest watch rejimida. jest.config.js yo'q: sozlama react-scripts ichida yashiringan, o'zgartirish uchun package.json dagi "jest" kaliti (cheklangan) yoki npm run eject (qaytarib bo'lmaydi — hamma konfiguratsiya loyihaga to'kiladi). Bunday loyihani yangilashning odatiy yo'li — Vite + Vitest'ga ko'chish; testlarning o'zi (RTL bilan yozilgan bo'lsa) deyarli o'zgarmaydi.

5.2 Enzyme — tanib oling, yozmang

Enzyme — Airbnb'ning React test kutubxonasi, 2016–2019 yillarda eng mashhuri. Uni tanish kerak, chunki eski kodda uchraydi:

jsx
import { shallow } from "enzyme";
import Counter from "./Counter";

test("bosilsa son oshadi", () => {
  const wrapper = shallow(<Counter />);
  wrapper.find("button.plus").simulate("click");
  expect(wrapper.state("count")).toBe(1);
  expect(wrapper.find(".count").text()).toBe("1");
});

Nega test yozamiz darsidagi "implementatsiyani test qilmang" qoidasining deyarli hamma buzilishi shu yerda:

  • shallow — bolalar komponentlarini chizmaydi: komponent yolg'iz, integratsiya sinalmaydi.
  • find("button.plus"), find(".count") — CSS sinflari bo'yicha qidirish; sinf nomi o'zgarsa test yiqiladi.
  • wrapper.state("count") — komponentning ichki holati. Hook'li funksiya komponentida bunday narsa umuman yo'q.
  • simulate("click") — haqiqiy hodisa emas, React handler'ini to'g'ridan chaqirish.

Enzyme'ning oxirgi versiyasi 3.11.0 — 2019-12-20. Rasmiy adapter faqat React 16 gacha (enzyme-adapter-react-16). React 17–19 uchun faqat norasmiy adapterlar bor — React 19 adapterining npm'dagi tavsifida o'zi "probably shouldn't use" ("ehtimol ishlatmasligingiz kerak") deb yozilgan. Ya'ni Enzyme'li loyihani React 18/19 ga ko'tarish — testlarni RTL'ga qayta yozish demak. Mashqda buni qilasiz.

Enzyme RTL
shallow(<X />), mount(<X />) render(<X />) (doim to'liq)
wrapper.find(".count") screen.getByRole(…), getByText(…)
.simulate("click") await user.click(…)
wrapper.state("count") ekrandagi natija: toHaveTextContent("1")
wrapper.setProps({...}) rerender(<X … />)

6. Ko'chirishning jim tuzoqlari

Jest → Vitest ko'chirishda ko'p narsa "o'zi ishlaydi". Xavflisi — jim farqlar: test yiqilmaydi, lekin boshqa narsani tekshiradi.

6.1 done callback

Eski Jest testlarida async kod done bilan kutiladi. Biz bir xil faylni ikkala vositada ishga tushirdik — tekshiruv ataylab xato (1 + 1 ni 3 deb kutadi):

js
test("done bilan (Jest uslubi)", (done) => {
  setTimeout(() => {
    expect(1 + 1).toBe(3);
    done();
  }, 10);
});

Jest 30 — to'g'ri yiqildi:

text
FAIL src/done.test.js (6.322 s)
  × done bilan (Jest uslubi) (5019 ms)

  ● done bilan (Jest uslubi)

    expect(received).toBe(expected) // Object.is equality

    Expected: 3
    Received: 2

Vitest 5.0.4 — o'tdi:

text
 ✓ t/done.test.js (1 test) 4ms
 Test Files  1 passed (1)
      Tests  1 passed (1)

Vitest done callback'ni qo'llamaydi: birinchi argument — test konteksti obyekti, done emas. Funksiya darhol tugadi, setTimeout ichidagi tekshiruv test tugagandan keyin ishladi va hech kim uni eshitmadi. Yolg'on yashil. Ko'chirishda done li har testni Promise'ga aylantiring:

js
test("Promise bilan", async () => {
  await new Promise((resolve) => setTimeout(resolve, 10));
  expect(1 + 1).toBe(2);
});

Qidirish uchun: grep -rn "(done)" src/.

6.2 mockReset

Bir xil nom, boshqa xulq:

js
const price = jest.fn(() => 35000); // Vitest'da: vi.fn(() => 35000)
price.mockReset();
console.log("natija:", price());

Biz sinadik: Jest 30 — natija: undefined (implementatsiya o'chadi), Vitest 5 — natija: 35000 (boshlang'ich implementatsiyaga qaytadi; Vitest II da ko'rgan edik). afterEach da mockReset chaqiradigan eski testlar Vitest'da boshqacha holatdan boshlanadi.

6.3 Soxta modul fabrikasi va requireActual

js
// Jest (CommonJS): module.exports = fetchMenu — fabrika
// funksiyaning o'zini qaytaradi; asl modul — sinxron
jest.mock("./api", () => jest.fn());
const asl = jest.requireActual("./format");

// Vitest (ESM): default eksport — `default` kaliti bilan;
// asl modul — Promise
vi.mock("./api", () => ({ default: vi.fn() }));
const asl = await vi.importActual("./format");

Vitest hujjatining ko'chirish bo'limi yana bir nechta farqni sanaydi: snapshot faylining sarlavhasi (// Jest Snapshot v1 → // Vitest Snapshot v1), globals sukut bo'yicha o'chiq. Ko'chirishdan oldin o'sha ro'yxatni o'qing.

7. Kanon tajribasi: vazifalar Jest'da (holatsiz)

Bu darsda vazifalar ga qadam yo'q — kanon v12 = holat 15. Uning o'rniga savolga javob: "vazifalar ni Jest'da ishlatsak nima bo'ladi?". Holat 15 nusxasi alohida papkada Jest 30.5.2, ts-jest 29.4.14 (ESM preset) va jest-environment-jsdom 30.5.2 bilan ishga tushirildi.

7.1 O'rnatish va konfiguratsiya

O'rnatish — 26 s, node_modules 436 → 480 MB (+44 MB), lock faylga +304 yozuv. jest-environment-jsdom o'zi bilan jsdom 26.1.0 ni olib keldi — loyihada Vitest uchun jsdom 30.1.2 allaqachon bor: ikki nusxa. Konfiguratsiya — 4 fayl, 60 qator: jest.config.js, vitest shim'i, CSS o'rinbosari va tsconfig.jest.json. jest.config.js dan bir bo'lak:

js
const umumiy = {
  ...ts,
  extensionsToTreatAsEsm: [".ts", ".tsx"],
  moduleNameMapper: {
    "^vitest$": "<rootDir>/jest-vitest-shim.js",
    "^@/(.*)$": "<rootDir>/src/$1",
  },
  globals: { __RELIZ__: "test" },
};

^@/(.*)$ — Vite'dagi @/ taxallusi qayta yozildi; __RELIZ__ — Vite'ning define i. Vitest bularni vite.config.ts dan o'zi oladi.

7.2 Uch tuzatish

Birinchi ishga tushirishlar ketma-ket uch xato berdi:

  1. './vazifa.js' topilmadi. tsconfig'dagi rewriteRelativeImportExtensions — .ts importlarni .js ga aylantiradi, Jest esa .js faylni qidiradi. Sozlama Jest uchun olib tashlandi.
  2. ReferenceError: Request is not defined — 18 ta komponent faylining hammasida. MSW Node'ning fetch/Request iga tayanadi, jest-environment-jsdom esa Node globallarini bermaydi. Yechim — qo'shimcha paket jest-fixed-jsdom 0.0.11.
  3. Cannot read properties of undefined (reading 'BASE_URL') — import.meta.env Vite'niki, Jest'da yo'q. Yechim — yana bir paket ts-jest-mock-import-meta 1.3.2 (qiymatlar konfiguratsiyada qo'lda yozildi).

7.3 Natija: 283/329

text
Test Suites: 22 failed, 33 passed, 55 total
Tests:       46 failed, 283 passed, 329 total

Vitest'da o'sha holat — 349/349. Jest 329 testni yig'di (20 tasi umuman yig'ilmadi), 46 tasi yiqildi; 55 fayldan 22 tasi qizil. Vaqt: Jest 33.6 / 39.8 s, Vitest o'sha mashinada 19.6 s (issiq).

Yiqilish sabablari (log'dagi xabarlar soni; performance.measure log'da ikki shaklda yozilgan — bu yerda bir marta):

Sabab Xabar Soni
jsdom 26 FormData(form, submitter) 20
jsdom 26 performance.measure 10
Jest'ning vm konteksti assert.deepStrictEqual 16
Jest'da yo'q API vi.stubGlobal, unstubAllGlobals, vi.waitFor 26
Jest qoidasi async describe taqiqlangan 4
Vite sintaksisi ?worker&url import 2

Eng qiziqi — deepStrictEqual. Xabarda "Expected" va "Received" bir xil ko'rinadi, lekin test yiqiladi. Sabab: Jest har test faylini alohida vm kontekstida ishlatadi. U yerda yaratilgan massiv boshqa "dunyo"ning (realm) Array idan. node:assert ning qat'iy tengligi prototipni ham solishtiradi. Buni Node'ning o'zida takrorlash mumkin:

js
import assert from "node:assert";
import vm from "node:vm";

const dishes = vm.runInNewContext('["Osh", "Manti"]');
console.log(Array.isArray(dishes), dishes instanceof Array);
assert.deepEqual(dishes, ["Osh", "Manti"]);
console.log("deepEqual: o'tdi");
assert.deepStrictEqual(dishes, ["Osh", "Manti"]);

Konsolda:

text
true false
deepEqual: o'tdi
AssertionError [ERR_ASSERTION]: Values have same structure but are not reference-equal:

Array.isArray — true, instanceof Array — false: massiv, lekin boshqa Array dan. Tarjima: "qiymatlar tuzilishi bir xil, lekin havolasi teng emas". vazifalar dagi 255 ta node:test testi (node:test dan beri) aynan node:assert bilan yozilgan — Vitest ularni bitta realm'da ishlatadi, Jest — yo'q.

7.4 Nega kanonga kirmadi

  • Narx: +44 MB, 2 qo'shimcha paket, 60 qator konfiguratsiya, jsdom ikki nusxa.
  • Eksperimental bayroq: --experimental-vm-modules — ESM Jest'da hali barqaror emas.
  • Ikki marta sozlash: taxallus, define, import.meta.env — Vite'da bor narsa Jest'ga qayta yozildi.
  • 46 test — ko'chirish uchun qo'shimcha ish, foydasi yo'q.

Xulosa: eski loyihada Jest uchraydi — uni o'qing va hurmat qiling; yangi Vite loyihasida — Vitest. Jest'li loyihani ko'chirish odatda bitta yo'nalishda: Jest → Vitest.

8. Ko'chirish tartibi

  1. Hozirgi holatni o'lchang: npx jest — nechta test, nechta yiqiladi, vaqt. Ko'chirishdan keyin shu raqam bilan solishtirasiz.
  2. O'rnating: vitest, kerak bo'lsa jsdom; vitest.config.ts da environment, setupFiles, include. Ko'p faylli loyihada vaqtincha globals: true — har faylga import yozmaslik uchun.
  3. Nomlarni almashtiring: jest. → vi., jest.requireActual → await vi.importActual.
  4. Jim tuzoqlarni qidiring: (done), mockReset, vi.mock fabrikasidagi default eksport, jest.setTimeout.
  5. Snapshot'larni qayta yozing: npx vitest run -u — va farqni ko'zdan kechiring.
  6. Ikkalasini parallel ishlating — raqamlar mos kelguncha. Keyin Jest paketlari va jest.config.js ni o'chiring.

9. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
Must use import to load ES Module Jest CommonJS'da Babel o'girishi yoki --experimental-vm-modules
ReferenceError: require is not defined ESM'da jest.mock jest.unstable_mockModule + import()
CSS importida SyntaxError Jest CSS'ni JS deb o'qiydi moduleNameMapper → o'rinbosar
Request is not defined jsdom muhitida Node globallari yo'q jest-fixed-jsdom
Vitest'da done li test doim yashil done qo'llanmaydi async / Promise
mockReset dan keyin boshqa qiymat Jest — undefined, Vitest — asl testni aniq yozing
wrapper.state is not a function hook'li komponent RTL: ekrandagi natija
sozlash fayli ishlamaydi setupFilesAfterEach (xato nom) setupFilesAfterEnv

10. Mashqlar

Mashqlar kurs/mashqlar/22/16-jest-vitest/ da. Jonli yechimlarni «Ishga tushir» bilan sinang.

1-mashq (oson): bo'sh joylarni to'ldiring

  • jest.fn() ning Vitest'dagisi: .fn()
  • jest.requireActual("./x") ning Vitest'dagisi: await vi.("./x")
  • Jest'ning sozlash fayli maydoni: setupFilesAfter
  • Jest'da ESM uchun Node bayrog'i: --experimental--modules
  • CRA'da test buyrug'i: react-scripts
Yechim
  • vi — Vitest'ning jest obyektiga o'xshashi; import qilinadi.
  • importActual — Promise qaytaradi, await bilan.
  • Env — "muhit tayyor bo'lgach" (jsdom o'rnatilgach) ishlaydi.
  • vm — Node'ning VM modullari; hali eksperimental.
  • test — Jest'ni watch rejimida ishga tushiradi.

2-mashq (o'rta): Jest testini Vitest'ga ko'chiring

Eski test (Jest, done bilan):

js
const { loadMenu } = require("./menu");

test("menyu yuklanadi", (done) => {
  const fetchMenu = jest.fn().mockResolvedValue(["Osh", "Manti"]);
  loadMenu(fetchMenu).then((dishes) => {
    expect(dishes).toEqual(["Osh", "Manti"]);
    expect(fetchMenu).toHaveBeenCalledTimes(1);
    done();
  });
});

test("server xatosi — bo'sh menyu", (done) => {
  const fetchMenu = jest.fn().mockRejectedValue(new Error("500"));
  loadMenu(fetchMenu).then((dishes) => {
    expect(dishes).toEqual([]);
    done();
  });
});

loadMenu(fetchMenu) — fetchMenu() natijasini qaytaradi, xato bo'lsa bo'sh massiv. Vitest'ga ko'chiring: import, vi.fn, done o'rniga async/await. loadMenu ni o'zingiz yozing. Keyin expect ni ataylab buzib, test yiqilishini tekshiring.

Yechim
jsx
import { expect, test, vi } from "vitest";

async function loadMenu(fetchMenu) {
  try {
    return await fetchMenu();
  } catch {
    return [];
  }
}

test("menyu yuklanadi", async () => {
  const fetchMenu = vi.fn().mockResolvedValue(["Osh", "Manti"]);
  const dishes = await loadMenu(fetchMenu);
  expect(dishes).toEqual(["Osh", "Manti"]);
  expect(fetchMenu).toHaveBeenCalledTimes(1);
});

test("server xatosi — bo'sh menyu", async () => {
  const fetchMenu = vi.fn().mockRejectedValue(new Error("500"));
  await expect(loadMenu(fetchMenu)).resolves.toEqual([]);
});

test("buzilgan tekshiruv yiqiladi", async () => {
  const fetchMenu = vi.fn().mockResolvedValue(["Osh"]);
  expect(await loadMenu(fetchMenu)).toEqual(["Manti"]);
});

Konsolda:

text
  ✓ menyu yuklanadi
  ✓ server xatosi — bo'sh menyu
  × buzilgan tekshiruv yiqiladi
    → expected [ 'Osh' ] to deeply equal [ 'Manti' ]
Testlar: 1 yiqildi, 2 o'tdi (jami 3)

Uchinchi test — ko'chirishning tekshiruvi: await bilan yozilgan test buzilganda yiqiladi. done li asl testni Vitest'ga import bilan ko'chirganingizda u yashil qolardi va hech narsani tekshirmasdi. Uchinchi testni o'chiring — u faqat isbot uchun.

3-mashq (qiyin): Enzyme testini RTL'ga qayta yozing

Eski Enzyme testi «Bahor» savatchasining son tugmalari uchun:

jsx
const wrapper = mount(<QtyPicker max={3} />);
wrapper.find(".plus").simulate("click");
wrapper.find(".plus").simulate("click");
expect(wrapper.find(".qty").text()).toBe("2");
wrapper.find(".plus").simulate("click");
wrapper.find(".plus").simulate("click");
expect(wrapper.find(".qty").text()).toBe("3");
expect(wrapper.find(".plus").prop("disabled")).toBe(true);
expect(wrapper.find(".minus").prop("disabled")).toBe(false);

QtyPicker ni hook'li funksiya komponent sifatida yozing (tugmalar faqat −/+ belgili — nom bering!) va testni RTL + user-event bilan qayta yozing: rol va nom bo'yicha qidirish, son ekrandagi matndan, toBeDisabled. Son o'zgarganda ekran o'quvchi eshitsin (aria-live).

Ishora: <button aria-label="Ko'paytirish">+</button>; son — <output aria-live="polite">, uning roli status.

Yechim
jsx
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { useState } from "react";
import { expect, test } from "vitest";

function QtyPicker({ max }) {
  const [qty, setQty] = useState(1);
  return (
    <div role="group" aria-label="Soni">
      <button
        aria-label="Kamaytirish"
        disabled={qty === 1}
        onClick={() => setQty(qty - 1)}
      >
        −
      </button>
      <output aria-live="polite">{qty}</output>
      <button
        aria-label="Ko'paytirish"
        disabled={qty === max}
        onClick={() => setQty(qty + 1)}
      >
        +
      </button>
    </div>
  );
}

test("son max gacha oshadi, keyin tugma o'chadi", async () => {
  const user = userEvent.setup();
  render(<QtyPicker max={3} />);
  const plus = screen.getByRole("button", { name: "Ko'paytirish" });
  const minus = screen.getByRole("button", { name: "Kamaytirish" });
  const qty = screen.getByRole("status");
  expect(minus).toBeDisabled();
  await user.click(plus);
  expect(qty).toHaveTextContent("2");
  await user.click(plus);
  console.log(qty.textContent);
  expect(plus).toBeDisabled();
  expect(minus).toBeEnabled();
  await user.click(plus);
  expect(qty).toHaveTextContent("3");
});

Konsolda:

text
3
  ✓ son max gacha oshadi, keyin tugma o'chadi
Testlar: 1 o'tdi (jami 1)

Farqlar: Enzyme testi .plus sinfiga bog'langan edi va tugmaning nomsizligini sezmasdi. RTL testi nomsiz tugmani topa olmaydi — shuning uchun komponentga aria-label qo'shildi, Malika uchun ham. Oxirgi qadam yangi tekshiruv: o'chirilgan tugmani bosish sonni o'zgartirmaydi (user.click haqiqiy foydalanuvchidek — disabled tugmada click bo'lmaydi). Enzyme'ning simulate i esa handler'ni to'g'ridan chaqirardi.

4-mashq: kanon tajribasini takrorlang (ixtiyoriy)

vazifalar nusxasini alohida papkaga oling (asosiy repo emas!) va Jest'da ishga tushirib ko'ring: jest@30.5.2, ts-jest, jest-environment-jsdom, vitest shim'i, node --experimental-vm-modules.

Yechim

Tekshiruv ro'yxati:

  • Birinchi ishga tushirishdagi xatolarni tartib bilan yozing: kengaytma, Request is not defined, import.meta.env — har biri uchun sabab va yechim.
  • Uchala tuzatishdan keyin natijani kanon bilan solishtiring (283/329). Farq bo'lsa — versiyalarni tekshiring.
  • node_modules hajmini oldin va keyin o'lchang (kanonda +44 MB).
  • Bitta deepStrictEqual yiqilishini toping va «Natija: 283/329» bo'limidagi vm sababini o'z faylingizda tushuntiring.
  • Papkani o'chiring; asosiy repo o'zgarmadi (git status toza).

11. Real ishda

  • Ish joyida Jest'li loyihalar hali ko'p: React Native (Jest — sukut vosita), eski CRA va Next.js loyihalari, Node backend'lar. Jest 30 faol rivojlanmoqda (30.5.2 — 2026-09).
  • Ko'chirish qarori — jamoa ishi: tezlik va ESM foydasi ko'chirish narxidan katta bo'lsa. Kichik loyihada bir kun, katta monorepo'da — haftalar; ikkala vosita bir muddat parallel ishlaydi.
  • Kod ko'rib chiqishda: done, shallow, wrapper.state, container.querySelector — eski uslub belgilari.
  • Intervyu savollari: "Jest va Vitest farqi?", "jest.mock nega fayl boshiga ko'tariladi?", "Enzyme'dan RTL'ga nega o'tishdi?", "Jest'da ESM bilan qanday muammo bor?".

Xulosa

  • Jest API'si Vitest'niki bilan deyarli bir xil: jest.fn ↔ vi.fn, jest.mock ↔ vi.mock; Jest'da describe/expect global.
  • Jest Vite'ni bilmaydi: transformer (babel-jest, ts-jest, @swc/jest), moduleNameMapper, CSS o'rinbosari — qo'lda.
  • ESM — Jest'ning og'riqli joyi: --experimental-vm-modules, jest.unstable_mockModule.
  • CRA (2025 da to'xtatilgan) — Jest 27 + RTL; Enzyme (2019 dan yangilanmagan) — implementatsiya testi, RTL'ga qayta yoziladi.
  • Ko'chirishdagi jim tuzoqlar: done (Vitest'da yolg'on yashil), mockReset, default eksportli soxta modul.
  • Kanon tajribasi: vazifalar Jest'da — +44 MB, 60 qator, 283/329; Vitest'da 349/349.

Keyingi dars: Next.js nima va qachon kerak — 23-qism boshlanadi: React ustidagi framework, server render va fayl asosidagi marshrutlar.

Manbalar

  • Jest: Configuration (jestjs.io/docs/configuration), ECMAScript Modules (jestjs.io/docs/ecmascript-modules), jest.unstable_mockModule
  • Vitest: Migrating from Jest (vitest.dev/guide/migration), vi.importActual, globals
  • React blog: "Sunsetting Create React App" (react.dev/blog, 2025-02-14)
  • Enzyme (github.com/enzymejs/enzyme); npm: enzyme 3.11.0 (2019-12-20), enzyme-adapter-react-16 1.15.8
  • Testing Library: "Migrate from Enzyme" (testing-library.com/docs/react-testing-library/migrate-from-enzyme)
  • Haqiqiy chiqishlar: Jest 30.5.2, jest-environment-jsdom 30.5.2 (jsdom 26.1.0), Vitest 5.0.4, Node 24.21.0, Windows 11 (2026-10-20)
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Jest va eski test muhitlari: Jest'li loyihani o'qish va Vitest'ga ko'chirish — IlmHamroh