Web razvoj
ReactTestiranjeMSWStorybookVitestReact Testing LibraryCI/CD

Testiranje ugovora za React komponente: MSW + Storybook kao živi API mockovi

AO
Adrijan Omićević
·13 min čitanja

# Što ćete izgraditi i zašto je važno#

Testiranje ugovora za React komponente znači validaciju komponente prema realističnom skupu API ponašanja: uspjeh, prazna stanja, validacijske greške, timeoute i padove autorizacije. Fokus nije samo na renderiranju, nego na ugovoru između UI-ja i backenda.

Ovo je važno jer regresije u UI-ju često nastaju zbog “razilaženja mockova”: Storybook koristi jedan lažni odgovor, testovi drugi, a produkcija treći. Rezultat su nestabilan UI, nedosljedna stanja i bugovi koji se pojave tek nakon izdanja.

Ovaj vodič pokazuje praktičan setup u kojem su MSW handleri jedini izvor istine i za Storybook i za testove. Dobivate žive API mockove, pregledive kroz PR-ove i provedive u CI-u.

Ako želite širi kontekst piramide testiranja i toolchaina, prvo pročitajte našu osnovnu strategiju: Strategija testiranja Reacta s Vitestom, React Testing Libraryjem i MSW-om.

# Preduvjeti#

ZahtjevVerzijaNapomene
Node.js18+Preporučen LTS
React18+Radi i s 19, ali primjeri koriste konvencije iz 18
Storybook7.6+Primjeri pretpostavljaju modernu Storybook konfiguraciju
MSW2+Koristi http i HttpResponse API-je
Vitest1.5+Radi s React aplikacijama temeljenim na Viteu
React Testing Library14+Za testove usmjerene na korisnika

# Što “testiranje ugovora” znači na razini komponente#

Testiranje ugovora na razini komponente nije o provjeri ispravnosti backend sheme. Radi se o provjeri ponašanja UI-ja za poznate API ugovore.

Ugovor komponente uključuje:

Element ugovoraPrimjerŠto asertirati
Oblik zahtjevaGET /api/orders?page=1Komponenta šalje očekivani URL i parametre
Oblik odgovoraitems, total, paginacijaUI ispravno renderira podatke i paginaciju
Ponašanje u grešci401, 500, 422Ispravne poruke, retry akcije i navigacija
Vremenska komponentaspori odgovoriLoading skeletoni i onemogućene akcije
Rubni slučajeviprazni nizovi, izostavljena opcionalna poljaTekst za prazno stanje i fallback UI

Dobar paket contract testova obično pokriva 6 do 12 “UI-smislenih” API scenarija po featureu. To je dovoljno da spriječi većinu regresija bez eksplozije troška održavanja.

🎯 Ključna poruka: MSW handlere tretirajte kao verzionirane, pregledive “API ugovore” za UI, a ne kao potrošne testne stubove.

# Struktura direktorija: jedan izvor istine za mockove#

Najjednostavniji način da spriječite razilaženje jest staviti handlere u zajednički modul i importati ih i u Storybook i u test setup.

Praktična struktura:

PutanjaSvrha
src/mocks/handlers/Zajednički request handleri, grupirani po domeni
src/mocks/scenarios/Složeni skupovi handlera za specifična stanja
src/mocks/browser.tsMSW worker za Storybook i lokalni razvoj
src/mocks/server.tsMSW server za unit i integracijske testove
src/mocks/test-data/Factory funkcije i fixturei koje handleri koriste
src/components/...Komponente i storyji

Ova podjela drži “što API radi” u handlerima, a “koje stanje želimo” u scenarijima.

# Korak 1: Definirajte skup handlera po domeni (zajednički)#

Napravite jedan handler file po domeni. Primjer: narudžbe (orders).

TypeScript
// src/mocks/handlers/orders.handlers.ts
import { http, HttpResponse, delay } from 'msw';
 
type Order = { id: string; status: 'paid' | 'pending'; totalCents: number };
 
const orders: Order[] = [
  { id: 'ord_1', status: 'paid', totalCents: 1299 },
  { id: 'ord_2', status: 'pending', totalCents: 4999 },
];
 
export const ordersHandlers = [
  http.get('/api/orders', async () => {
    await delay(150);
    return HttpResponse.json({ items: orders, total: orders.length });
  }),
];

Nekoliko praktičnih pravila:

  1. 1
    Držite handlere blizu načina na koji se stvarni API ponaša. Ako produkcija vraća items i total, nemojte vraćati data.
  2. 2
    Dodajte male odgode (delay) kao default. Mnogi UI bugovi vezani su uz timing i nikad se ne pojave s instant mockovima.
  3. 3
    Koristite realistične identifikatore i vrijednosti, ne id: 1. Želite uhvatiti rubne slučajeve u formatiranju stringova i copyju.

⚠️ Upozorenje: Izbjegavajte pisanje handlera koji su “previše savršeni”. Ako produkcijski API ponekad vraća prazne liste ili djelomična opcionalna polja, modelirajte to kroz scenarije. Savršeni mockovi stvaraju lažnu sigurnost.

# Korak 2: Sastavljajte scenarije umjesto dupliciranja handlera#

Scenariji su imenovani skupovi handlera koji predstavljaju UI stanja. Tu testiranje ugovora postaje održivo.

TypeScript
// src/mocks/scenarios/orders.scenarios.ts
import { http, HttpResponse, delay } from 'msw';
import { ordersHandlers } from '../handlers/orders.handlers';
 
export const ordersScenario = {
  default: () => [...ordersHandlers],
 
  empty: () => [
    http.get('/api/orders', async () => {
      await delay(100);
      return HttpResponse.json({ items: [], total: 0 });
    }),
  ],
 
  serverError: () => [
    http.get('/api/orders', async () => {
      await delay(100);
      return HttpResponse.json({ message: 'Internal error' }, { status: 500 });
    }),
  ],
 
  unauthorized: () => [
    http.get('/api/orders', async () => {
      await delay(100);
      return HttpResponse.json({ message: 'Unauthorized' }, { status: 401 });
    }),
  ],
};

Zašto ovo pomaže:

  • Storybook storyji se čisto mapiraju na scenarije: “OrdersList Empty”, “OrdersList Error”.
  • Testovi mogu ponovno koristiti scenarije bez prepisivanja mockova.
  • Kad se API promijeni, ažurirate scenarije i svi potrošači vide promjenu.

# Korak 3: MSW postavke za testove (Vitest)#

Napravite setup testnog servera koji se importa u test runner.

TypeScript
// src/mocks/server.ts
import { setupServer } from 'msw/node';
import { ordersScenario } from './scenarios/orders.scenarios';
 
export const server = setupServer(...ordersScenario.default());

Zatim ga povežite s Vitestom.

TypeScript
// src/test/setup.ts
import '@testing-library/jest-dom/vitest';
import { afterAll, afterEach, beforeAll } from 'vitest';
import { server } from '../mocks/server';
 
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }));
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

Ova konfiguracija radi dvije važne stvari:

  • onUnhandledRequest: 'error' pretvara nedostajuće mockove u pad testova. To je mehanizam provedbe ugovora.
  • resetHandlers() sprječava “kontaminaciju” između testova, smanjujući nestabilnost.

# Korak 4: MSW postavke za Storybook (isti handleri)#

U Storybooku koristite MSW addon i učitajte iste scenarije handlera.

TypeScript
// .storybook/preview.ts
import type { Preview } from '@storybook/react';
import { initialize, mswLoader } from 'msw-storybook-addon';
import { ordersScenario } from '../src/mocks/scenarios/orders.scenarios';
 
initialize({ onUnhandledRequest: 'bypass' });
 
const preview: Preview = {
  loaders: [mswLoader],
  parameters: {
    msw: {
      handlers: ordersScenario.default(),
    },
  },
};
 
export default preview;

Odabir onUnhandledRequest se razlikuje:

  • U testovima želite “error” kako biste proveli pokrivenost.
  • U Storybooku “bypass” može biti prihvatljiv dok gradite nove komponente, ali timovi često pređu na “warn” kad sazriju.

ℹ️ Napomena: Storybook koristi Service Worker okruženje, dok Vitest koristi Node interceptor. Iste definicije handlera rade u oba slučaja jer MSW apstrahira razlike runtimea.

# Korak 5: Storyji kao živa dokumentacija ugovora#

Sada možete pisati storyje koji eksplicitno deklariraju koji scenarij predstavljaju. Time vaš Storybook postaje katalog ugovora.

TypeScript
// src/components/OrdersList/OrdersList.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { OrdersList } from './OrdersList';
import { ordersScenario } from '../../mocks/scenarios/orders.scenarios';
 
const meta: Meta<typeof OrdersList> = {
  title: 'Orders/OrdersList',
  component: OrdersList,
};
export default meta;
 
type Story = StoryObj<typeof OrdersList>;
 
export const Default: Story = {
  parameters: { msw: { handlers: ordersScenario.default() } },
};
 
export const Empty: Story = {
  parameters: { msw: { handlers: ordersScenario.empty() } },
};
 
export const ServerError: Story = {
  parameters: { msw: { handlers: ordersScenario.serverError() } },
};

Praktične prednosti:

  • Product i QA mogu pregledati UI stanja bez potrebe za backend seed podacima.
  • Developeri mogu odmah reproducirati nezgodna stanja greške.
  • Scenariji handlera postaju “živa specifikacija” kako UI očekuje da se API ponaša.

Ako održavate design system, uskladite ova stanja s design tokenima, loading obrascima te empty/error komponentama. Lakše je zadržati konzistentnost kad su tokeni i UI primitive standardizirani: Tokeni design systema s Tailwindom i Radixom.

# Korak 6: Contract testovi koji ponovno koriste scenarije#

Contract test ne bi trebao ponovno graditi mockove. Trebao bi odabrati scenarije i asertirati korisniku vidljivo ponašanje.

Primjer s React Testing Library:

TypeScript
// src/components/OrdersList/OrdersList.test.tsx
import { render, screen } from '@testing-library/react';
import { server } from '../../mocks/server';
import { ordersScenario } from '../../mocks/scenarios/orders.scenarios';
import { OrdersList } from './OrdersList';
 
it('renders orders from the API', async () => {
  server.use(...ordersScenario.default());
  render(<OrdersList />);
 
  expect(await screen.findByText('ord_1')).toBeInTheDocument();
  expect(screen.getByText('ord_2')).toBeInTheDocument();
});
 
it('shows an empty state when there are no orders', async () => {
  server.use(...ordersScenario.empty());
  render(<OrdersList />);
 
  expect(await screen.findByText(/no orders/i)).toBeInTheDocument();
});
 
it('shows an error message on server error', async () => {
  server.use(...ordersScenario.serverError());
  render(<OrdersList />);
 
  expect(await screen.findByText(/try again/i)).toBeInTheDocument();
});

Nekoliko pravila koja smanjuju nestabilnost:

  • Asertirajte tekst koji korisnici vide, ne implementacijske detalje.
  • Koristite findBy... za async sadržaj.
  • Držite testove fokusirane na ponašanje po scenariju, ne na svaki podkomponentni detalj.

# Kako dijeljenje handlera sprječava razilaženje i nestabilan UI#

Razilaženje nastaje kad imate više izvora istine:

  • Testovi mockaju fetch s items: [...].
  • Storybook vraća data: [...].
  • Produkcija vraća items: [...] plus total.

UI “radi” u jednom okruženju i puca u drugom.

Dijeljenje handlera to sprječava jer:

  • Svaka promjena oblika odgovora je jedan diff u src/mocks/handlers ili src/mocks/scenarios.
  • Storybook i testovi oba “pucaju” kad se ugovor prekine, što prisiljava usklađivanje.
  • Recenzenti mogu uočiti promjene API oblika u PR-ovima, a ne u bug reportovima.

U zrelim timovima promjene handlera tretiraju se kao promjene API-ja: zahtijevaju review i često traže ažuriranje UI-ja i testova u istom PR-u.

# Pojačajte strogoću ugovora: fail na neobrađene zahtjeve#

Strogoća je razlika između “lijepih demo mockova” i testiranja ugovora.

Preporučeni defaulti:

OkruženjeonUnhandledRequestZašto
VitesterrorPrisili da svaki request bude u ugovoru
Storybook lokalnowarn ili bypassPomaže brzini razvoja, ali dugoročno je warn bolji
CI Storybook testoviwarn ili errorSprječava tiho izostavljanje handlera u pregledanim stanjima

Ako vaše komponente koriste generirane API klijente, stroga MSW pokrivenost rano hvata “iznenadne pozive”, npr. novi endpoint koji se počne pozivati nakon refaktora.

# Workflow: CI, vizualni pregled i sprječavanje regresija#

Dobar workflow testiranja ugovora kombinira:

  1. 1
    Automatizirane contract testove u Vitestu.
  2. 2
    Vizualni pregled storyja za kritična stanja.
  3. 3
    Sprječavanje regresija kroz snapshotove i pregledane promjene mockova.

Primjer CI pipelinea (praktično i brzo)#

Pragmatičan CI pristup za React aplikaciju:

KorakTipično trajanjeŠto hvata
Typecheck i lint1 do 3 minuteNesigurne refaktore, kršenja pravila
Unit + contract testovi2 do 6 minutaUI-data regresije, obradu grešaka, loading stanja
Storybook build2 do 5 minutaSlomljene storyje, nedostajuće assete
Vizualni test run3 do 10 minutaCSS regresije, layout pomake, probleme s temama

Da pipeline ostane brz, ograničite vizualne testove na ključne storyje, ne na cijelu biblioteku.

CI naredbe (primjer)#

Ove naredbe prikazuju čestu osnovu.

Bash
# tests
pnpm test --run
 
# build Storybook to ensure it compiles
pnpm storybook:build

Za vizualne provjere timovi obično koriste Chromatic, Loki ili Playwright screenshot testove. Detalji ovise o vašem stacku, ali princip je isti: storyji trebaju predstavljati iste MSW scenarije koje koriste vaši testovi.

Sprječavanje regresija: povežite vizualne storyje sa scenarijima#

Napravite mali skup “contract-kritičnih” storyja, npr.:

  • Default uspješno stanje
  • Prazno stanje
  • Error stanje
  • Permission denied
  • Loading stanje na sporoj mreži

Tih pet stanja obično uhvati velik postotak UI regresija, posebno u aplikacijama bogatim podacima.

Praktična konvencija imenovanja:

Naziv storyjaScenarijZašto je važno
DefaultsuccessNajčešće korišten put
EmptyemptyČest rubni slučaj u novim računima
ServerError500Poruke i retry ponašanje
Unauthorized401Redirect na login, dozvole
Loadinglong delaySkeletoni, onemogućene akcije

Dodajte “Slow” scenarij za UI bugove vezane uz timing#

Timing bugovi su česti u Reactu: dvostruki spinneri, flicker, prerano ponovno omogućavanje gumba. Dodajte spori scenarij i pregledajte ga vizualno.

TypeScript
// src/mocks/scenarios/orders.scenarios.ts
import { http, HttpResponse, delay } from 'msw';
 
export const slowOrders = () => [
  http.get('/api/orders', async () => {
    await delay(2500);
    return HttpResponse.json({ items: [], total: 0 });
  }),
];

Koristite ga u storyju naziva Loading, i u testovima asertirajte da se skeletoni renderiraju te da su akcije onemogućene dok je request u tijeku.

Checklist za PR review: ugovor i UI zajedno#

Praktičan proces reviewa je zahtijevati:

  • Promjenu handlera ili scenarija kad se promijeni API ponašanje.
  • Ažuriranje storyja za svako novo UI stanje.
  • Contract test za svaki bug fix koji je uključivao dohvat podataka ili obradu grešaka.

Ovo se dobro uklapa u širu checklistu dizajna i kvalitete: Checklist za React design review: performanse, pristupačnost, održivost.

💡 Savjet: Stavite src/mocks/** pod CODEOWNERS review, slično kao backend API definicije. To prisiljava da promjene mockova budu namjerne i odvraća od “brzih fixova” koji ruše pokrivenost ugovora.

# Praktični obrasci koji skaliraju izvan jedne komponente#

Obrazac 1: Skupovi scenarija na razini “feature ugovora”#

Za feature stranicu grupirajte scenarije preko više endpointa. Primjer: OrdersPage može zvati narudžbe, korisnički profil i billing status.

Umjesto razbacivanja handlera kroz storyje, napravite feature-level scenarij file koji kompozira domenske handlere.

PristupPrednostiNedostaci
Scenariji po komponentiLako za startMože duplicirati cross-cutting requestove
Scenariji po featureuOdgovara realnim ekranimaTraži koordinaciju između komponenti
HibridNajbolje od obaTreba konvencije i disciplinu u code reviewu

Obrazac 2: Data factoryji za stabilnost#

Hardkodirani fixturei zastare. Factoryji drže fixturee konzistentnima i omogućuju randomizaciju kad je potrebna.

Pragmatično pravilo: stabilno po defaultu, randomizirano samo u posebnim fuzz testovima.

Obrazac 3: Contract pokrivenost za paginaciju, sortiranje i filtriranje#

Ovo su česti izvori regresija jer ovise o query parametrima.

Jednostavan pristup:

  • Napravite handler koji asertira query parametre i vraća različite payloadove.
  • Dodajte jedan Storybook story po query varijanti koja vam je bitna.

Čak i mali set storyja i testova ovdje hvata probleme poput pogrešnih naziva parametara i slomljenih labela za sortiranje.

# Česte zamke i kako ih izbjeći#

  1. 1

    Dupliciranje handlera u storyjima i testovima
    Rješenje: zajednički moduli handlera + kompozicija scenarija.

  2. 2

    Ne rušite testove na neobrađene zahtjeve
    Rješenje: provedite onUnhandledRequest: 'error' kako bi se uhvatili nedostajući ugovori.

  3. 3

    Mockanje oblika odgovora koji ne odgovaraju produkciji
    Rješenje: bazirajte handlere na stvarnim API odgovorima i ažurirajte ih kad se backend promijeni.

  4. 4

    Previše storyja bez strategije pregleda
    Rješenje: definirajte “contract-kritičan” subset za vizualnu regresiju, a ostatak držite informativnim.

  5. 5

    Korištenje instant odgovora posvuda
    Rješenje: dodajte defaultni delay i uključite barem jedan spori scenarij po featureu.

# Ključne poruke#

  • Definirajte MSW handlere jednom i dijelite ih između Storybooka i testova kako biste eliminirali razilaženje mockova.
  • Gradite imenovane scenarije za uspjeh, prazno stanje, grešku, neautorizirano i sporo stanje, pa ih ponovno koristite kroz storyje i contract testove.
  • Učinite testove strogima s onUnhandledRequest: 'error' kako biste proveli pokrivenost ugovora UI-prema-API-ju.
  • Tretirajte Storybook storyje kao živu dokumentaciju ugovora i u CI-u pokrećite vizualne provjere nad malim, visokovrijednim subsetom.
  • Promjene u src/mocks/** recenzirajte kao produkcijski kod, jer promjene handlera efektivno mijenjaju UI ugovor.

# Zaključak#

Testiranje ugovora za React komponente najbolje funkcionira kada su vaši mockovi zajednički, verzionirani asset. MSW plus Storybook daje vam žive API mockove koji poboljšavaju svakodnevni razvoj i značajno smanjuju nestabilan UI te kasne regresije.

Ako želite pomoć u standardizaciji ovoga kroz React codebase, uključujući Storybook governance, CI vizualnu regresiju i stabilan MSW sloj ugovora, javite se Samiodi i pomoći ćemo vam implementirati održiv workflow testiranja.

FAQ

Share
A
Adrijan OmićevićOsnivač i senior developer

Osnivač i senior developer u Samiodi. 8+ godina iskustva u izradi React, Next.js, Flutter i n8n rješenja za klijente diljem Europe.

Više iz kategorije Web razvoj

Sve

Trebate pomoć s projektom?

Gradimo prilagođena rješenja koristeći tehnologije iz ovog članka. Senior tim, fiksne cijene.