Hrvatski

Savladajte napredne Jest obrasce testiranja za izradu pouzdanijeg softvera koji se lakše održava. Istražite tehnike poput mockanja, snapshot testiranja i prilagođenih matchera za globalne razvojne timove.

Jest: Napredni obrasci testiranja za robustan softver

U današnjem brzom okruženju razvoja softvera, osiguravanje pouzdanosti i stabilnosti vaše kodne baze je od presudne važnosti. Iako je Jest postao de facto standard za JavaScript testiranje, prelazak s osnovnih jediničnih testova otključava novu razinu povjerenja u vaše aplikacije. Ovaj post bavi se naprednim obrascima Jest testiranja koji su ključni za izradu robusnog softvera, namijenjenog globalnoj publici programera.

Zašto ići dalje od osnovnih jediničnih testova?

Osnovni jedinični testovi provjeravaju pojedinačne komponente u izolaciji. Međutim, stvarne aplikacije su složeni sustavi u kojima komponente međusobno djeluju. Napredni obrasci testiranja rješavaju te složenosti omogućujući nam da:

Ovladavanje mockanjem i špijunima

Mockanje je ključno za izoliranje jedinice koja se testira zamjenom njezinih ovisnosti kontroliranim zamjenama. Jest za to pruža moćne alate:

jest.fn(): Temelj mockova i špijuna

jest.fn() stvara mock funkciju. Možete pratiti njezine pozive, argumente i povratne vrijednosti. Ovo je temeljni element za sofisticiranije strategije mockanja.

Primjer: Praćenje poziva funkcije

// component.js
export const fetchData = () => {
  // Simulira API poziv
  return Promise.resolve({ data: 'some data' });
};

export const processData = async (fetcher) => {
  const result = await fetcher();
  return `Processed: ${result.data}`;
};

// component.test.js
import { processData } from './component';

test('should process data correctly', async () => {
  const mockFetcher = jest.fn().mockResolvedValue({ data: 'mocked data' });
  const result = await processData(mockFetcher);
  expect(result).toBe('Processed: mocked data');
  expect(mockFetcher).toHaveBeenCalledTimes(1);
  expect(mockFetcher).toHaveBeenCalledWith();
});

jest.spyOn(): Promatranje bez zamjene

jest.spyOn() omogućuje vam promatranje poziva metode na postojećem objektu bez nužne zamjene njezine implementacije. Također možete mockati implementaciju ako je potrebno.

Primjer: Špijuniranje metode modula

// logger.js
export const logInfo = (message) => {
  console.log(`INFO: ${message}`);
};

// service.js
import { logInfo } from './logger';

export const performTask = (taskName) => {
  logInfo(`Starting task: ${taskName}`);
  // ... logika zadatka ...
  logInfo(`Task ${taskName} completed.`);
};

// service.test.js
import { performTask } from './service';
import * as logger from './logger';

test('should log task start and completion', () => {
  const logSpy = jest.spyOn(logger, 'logInfo');

  performTask('backup');

  expect(logSpy).toHaveBeenCalledTimes(2);
  expect(logSpy).toHaveBeenCalledWith('Starting task: backup');
  expect(logSpy).toHaveBeenCalledWith('Task backup completed.');

  logSpy.mockRestore(); // Važno je vratiti originalnu implementaciju
});

Mockanje uvoza modula

Jestove mogućnosti mockanja modula su opsežne. Možete mockati cijele module ili specifične izvoze.

Primjer: Mockanje vanjskog API klijenta

// api.js
import axios from 'axios';

export const getUser = async (userId) => {
  const response = await axios.get(`/api/users/${userId}`);
  return response.data;
};

// user-service.js
import { getUser } from './api';

export const getUserFullName = async (userId) => {
  const user = await getUser(userId);
  return `${user.firstName} ${user.lastName}`;
};

// user-service.test.js
import { getUserFullName } from './user-service';
import * as api from './api';

// Mockaj cijeli api modul
jest.mock('./api');

test('should get full name using mocked API', async () => {
  // Mockaj specifičnu funkciju iz mockanog modula
  api.getUser.mockResolvedValue({ id: 1, firstName: 'Ada', lastName: 'Lovelace' });

  const fullName = await getUserFullName(1);

  expect(fullName).toBe('Ada Lovelace');
  expect(api.getUser).toHaveBeenCalledTimes(1);
  expect(api.getUser).toHaveBeenCalledWith(1);
});

Automatsko mockanje vs. ručno mockanje

Jest automatski mocka Node.js module. Za ES module ili prilagođene module, možda će vam trebati jest.mock(). Za veću kontrolu, možete stvoriti __mocks__ direktorije.

Mock implementacije

Možete pružiti prilagođene implementacije za svoje mockove.

Primjer: Mockanje s prilagođenom implementacijom

// math.js
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;

// calculator.js
import { add, subtract } from './math';

export const calculate = (operation, a, b) => {
  if (operation === 'add') {
    return add(a, b);
  } else if (operation === 'subtract') {
    return subtract(a, b);
  }
  return null;
};

// calculator.test.js
import { calculate } from './calculator';
import * as math from './math';

// Mockaj cijeli math modul
jest.mock('./math');

test('should perform addition using mocked math add', () => {
  // Pruži mock implementaciju za funkciju 'add'
  math.add.mockImplementation((a, b) => a + b + 10); // Dodaj 10 rezultatu
  math.subtract.mockReturnValue(5); // Mockaj i subtract

  const result = calculate('add', 5, 3);

  expect(math.add).toHaveBeenCalledWith(5, 3);
  expect(result).toBe(18); // 5 + 3 + 10

  const subResult = calculate('subtract', 10, 2);
  expect(math.subtract).toHaveBeenCalledWith(10, 2);
  expect(subResult).toBe(5);
});

Snapshot testiranje: Očuvanje korisničkog sučelja i konfiguracije

Snapshot testovi su moćna značajka za bilježenje izlaza vaših komponenata ili konfiguracija. Posebno su korisni za testiranje korisničkog sučelja ili provjeru složenih struktura podataka.

Kako funkcionira snapshot testiranje

Prvi put kada se pokrene snapshot test, Jest stvara .snap datoteku koja sadrži serijalizirani prikaz testirane vrijednosti. Pri sljedećim pokretanjima, Jest uspoređuje trenutni izlaz s pohranjenim snapshotom. Ako se razlikuju, test ne uspijeva, upozoravajući vas na nenamjerne promjene. Ovo je neprocjenjivo za otkrivanje regresija u UI komponentama u različitim regijama ili lokalizacijama.

Primjer: Snapshotiranje React komponente

Pretpostavimo da imate React komponentu:

// UserProfile.js
import React from 'react';

const UserProfile = ({ name, email, isActive }) => (
  <div>
    <h2>{name}</h2>
    <p><strong>Email:</strong> {email}</p>
    <p><strong>Status:</strong> {isActive ? 'Active' : 'Inactive'}</p>
  </div>
);

export default UserProfile;

// UserProfile.test.js
import React from 'react';
import renderer from 'react-test-renderer'; // Za snimke React komponenata
import UserProfile from './UserProfile';

test('renders UserProfile correctly', () => {
  const user = {
    name: 'Jane Doe',
    email: 'jane.doe@example.com',
    isActive: true,
  };
  const component = renderer.create(
    <UserProfile {...user} />
  );
  const tree = component.toJSON();
  expect(tree).toMatchSnapshot();
});

test('renders inactive UserProfile correctly', () => {
  const user = {
    name: 'John Smith',
    email: 'john.smith@example.com',
    isActive: false,
  };
  const component = renderer.create(
    <UserProfile {...user} />
  );
  const tree = component.toJSON();
  expect(tree).toMatchSnapshot('inactive user profile'); // Imenovani snapshot
});

Nakon pokretanja testova, Jest će stvoriti datoteku UserProfile.test.js.snap. Kada ažurirate komponentu, morat ćete pregledati promjene i potencijalno ažurirati snapshot pokretanjem Jesta sa zastavicom --updateSnapshot ili -u.

Najbolje prakse za snapshot testiranje

Prilagođeni matcheri: Poboljšanje čitljivosti testova

Jestovi ugrađeni matcheri su opsežni, ali ponekad trebate provjeriti specifične uvjete koji nisu pokriveni. Prilagođeni matcheri omogućuju vam stvaranje vlastite logike provjere, čineći vaše testove izražajnijima i čitljivijima.

Stvaranje prilagođenih matchera

Možete proširiti Jestov expect objekt vlastitim matcherima.

Primjer: Provjera valjanog formata e-pošte

U vašoj Jest datoteci za postavljanje (npr. jest.setup.js, konfiguriranoj u jest.config.js):

// jest.setup.js

expect.extend({
  toBeValidEmail(received) {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    const pass = emailRegex.test(received);

    if (pass) {
      return {
        message: () => `expected ${received} not to be a valid email`,
        pass: true,
      };
    } else {
      return {
        message: () => `expected ${received} to be a valid email`,
        pass: false,
      };
    }
  },
});

// U vašem jest.config.js
// module.exports = { setupFilesAfterEnv: ['<rootDir>/jest.setup.js'] };

U vašoj testnoj datoteci:

// validation.test.js

test('should validate email formats', () => {
  expect('test@example.com').toBeValidEmail();
  expect('invalid-email').not.toBeValidEmail();
  expect('another.test@sub.domain.co.uk').toBeValidEmail();
});

Prednosti prilagođenih matchera

Testiranje asinkronih operacija

JavaScript je uvelike asinkron. Jest pruža izvrsnu podršku za testiranje promiseova i async/await.

Korištenje async/await

Ovo je moderan i najčitljiviji način testiranja asinkronog koda.

Primjer: Testiranje asinkrone funkcije

// dataService.js
export const fetchUserData = async (userId) => {
  // Simulira dohvaćanje podataka s odgodom
  await new Promise(resolve => setTimeout(resolve, 50));
  if (userId === 1) {
    return { id: 1, name: 'Alice' };
  } else {
    throw new Error('User not found');
  }
};

// dataService.test.js
import { fetchUserData } from './dataService';

test('fetches user data correctly', async () => {
  const user = await fetchUserData(1);
  expect(user).toEqual({ id: 1, name: 'Alice' });
});

test('throws error for non-existent user', async () => {
  await expect(fetchUserData(2)).rejects.toThrow('User not found');
});

Korištenje .resolves i .rejects

Ovi matcheri pojednostavljuju testiranje razrješenja i odbijanja promiseova.

Primjer: Korištenje .resolves/.rejects

// dataService.test.js (nastavak)

test('fetches user data with .resolves', () => {
  return expect(fetchUserData(1)).resolves.toEqual({ id: 1, name: 'Alice' });
});

test('throws error for non-existent user with .rejects', () => {
  return expect(fetchUserData(2)).rejects.toThrow('User not found');
});

Upravljanje timerima

Za funkcije koje koriste setTimeout ili setInterval, Jest pruža kontrolu nad timerima.

Primjer: Kontroliranje timera

// delayedGreeter.js
export const greetAfterDelay = (name, callback) => {
  setTimeout(() => {
    callback(`Hello, ${name}!`);
  }, 1000);
};

// delayedGreeter.test.js
import { greetAfterDelay } from './delayedGreeter';

jest.useFakeTimers(); // Omogući lažne timere

test('greets after delay', () => {
  const mockCallback = jest.fn();
  greetAfterDelay('World', mockCallback);

  // Pomakni timere za 1000ms
  jest.advanceTimersByTime(1000);

  expect(mockCallback).toHaveBeenCalledTimes(1);
  expect(mockCallback).toHaveBeenCalledWith('Hello, World!');
});

// Vrati stvarne timere ako su potrebni drugdje
jest.useRealTimers();

Organizacija i struktura testova

Kako vaš set testova raste, organizacija postaje ključna za održavanje.

Describe blokovi i It blokovi

Koristite describe za grupiranje povezanih testova i it (ili test) za pojedinačne testne slučajeve. Ova struktura odražava modularnost aplikacije.

Primjer: Strukturirani testovi

describe('User Authentication Service', () => {
  let authService;

  beforeEach(() => {
    // Postavi mockove ili instance servisa prije svakog testa
    authService = require('./authService');
    jest.spyOn(authService, 'login').mockImplementation(() => Promise.resolve({ token: 'fake_token' }));
  });

  afterEach(() => {
    // Očisti mockove
    jest.restoreAllMocks();
  });

  describe('login functionality', () => {
    it('should successfully log in a user with valid credentials', async () => {
      const result = await authService.login('user@example.com', 'password123');
      expect(result.token).toBeDefined();
      // ... dodatne provjere ...
    });

    it('should fail login with invalid credentials', async () => {
      jest.spyOn(authService, 'login').mockRejectedValue(new Error('Invalid credentials'));
      await expect(authService.login('user@example.com', 'wrong_password')).rejects.toThrow('Invalid credentials');
    });
  });

  describe('logout functionality', () => {
    it('should clear user session', async () => {
      // Testiraj logiku odjave...
    });
  });
});

Hooks za postavljanje i čišćenje

Ovi hookovi su ključni za postavljanje mock podataka, veza s bazom podataka ili čišćenje resursa između testova.

Testiranje za globalnu publiku

Prilikom razvoja aplikacija za globalnu publiku, razmatranja o testiranju se proširuju:

Internacionalizacija (i18n) i lokalizacija (l10n)

Osigurajte da se vaše korisničko sučelje i poruke ispravno prilagođavaju različitim jezicima i regionalnim formatima.

Primjer: Testiranje lokaliziranog formatiranja datuma

// dateUtils.js
export const formatLocalizedDate = (date, locale) => {
  return new Intl.DateTimeFormat(locale, { year: 'numeric', month: 'numeric', day: 'numeric' }).format(date);
};

// dateUtils.test.js
import { formatLocalizedDate } from './dateUtils';

test('formats date correctly for US locale', () => {
  const date = new Date(2023, 10, 15); // 15. studeni 2023.
  expect(formatLocalizedDate(date, 'en-US')).toBe('11/15/2023');
});

test('formats date correctly for German locale', () => {
  const date = new Date(2023, 10, 15);
  expect(formatLocalizedDate(date, 'de-DE')).toBe('15.11.2023');
});

Svijest o vremenskim zonama

Testirajte kako vaša aplikacija rukuje različitim vremenskim zonama, posebno za značajke poput zakazivanja ili ažuriranja u stvarnom vremenu. Mockanje sistemskog sata ili korištenje biblioteka koje apstrahiraju vremenske zone može biti korisno.

Kulturološke nijanse u podacima

Razmotrite kako se brojevi, valute i drugi prikazi podataka mogu različito percipirati ili očekivati u različitim kulturama. Prilagođeni matcheri ovdje mogu biti posebno korisni.

Napredne tehnike i strategije

Razvoj vođen testovima (TDD) i razvoj vođen ponašanjem (BDD)

Jest se dobro slaže s TDD (Crveno-Zeleno-Refaktoriranje) i BDD (Zadano-Kada-Tada) metodologijama. Napišite testove koji opisuju željeno ponašanje prije pisanja implementacijskog koda. To osigurava da se kod piše s testiranjem na umu od samog početka.

Integracijsko testiranje s Jestom

Iako se Jest ističe u jediničnim testovima, može se koristiti i za integracijske testove. Pomoći može mockanje manjeg broja ovisnosti ili korištenje alata poput Jestove opcije runInBand.

Primjer: Testiranje interakcije s API-jem (pojednostavljeno)

// apiService.js
import axios from 'axios';

const API_BASE_URL = 'https://api.example.com';

export const createProduct = async (productData) => {
  const response = await axios.post(`${API_BASE_URL}/products`, productData);
  return response.data;
};

// apiService.test.js (Integration test)
import axios from 'axios';
import { createProduct } from './apiService';

// Mockaj axios za integracijske testove kako bi se kontrolirao mrežni sloj
jest.mock('axios');

test('creates a product via API', async () => {
  const mockProduct = { id: 1, name: 'Gadget' };
  const responseData = { success: true, product: mockProduct };

  axios.post.mockResolvedValue({
    data: responseData,
    status: 201,
    headers: { 'content-type': 'application/json' },
  });

  const newProductData = { name: 'Gadget', price: 99.99 };
  const result = await createProduct(newProductData);

  expect(axios.post).toHaveBeenCalledWith(`${process.env.API_BASE_URL || 'https://api.example.com'}/products`, newProductData);
  expect(result).toEqual(responseData);
});

Paralelizam i konfiguracija

Jest može pokretati testove paralelno kako bi ubrzao izvršavanje. Konfigurirajte ovo u vašoj jest.config.js datoteci. Na primjer, postavljanje maxWorkers kontrolira broj paralelnih procesa.

Izvješća o pokrivenosti

Koristite Jestovo ugrađeno izvještavanje o pokrivenosti kako biste identificirali dijelove vaše kodne baze koji se ne testiraju. Pokrenite testove s --coverage kako biste generirali detaljna izvješća.

jest --coverage

Pregledavanje izvješća o pokrivenosti pomaže osigurati da vaši napredni obrasci testiranja učinkovito pokrivaju kritičnu logiku, uključujući putanje koda za internacionalizaciju i lokalizaciju.

Zaključak

Ovladavanje naprednim obrascima Jest testiranja značajan je korak prema izradi pouzdanog, održivog i visokokvalitetnog softvera za globalnu publiku. Učinkovitim korištenjem mockanja, snapshot testiranja, prilagođenih matchera i tehnika asinkronog testiranja, možete poboljšati robusnost vašeg seta testova i steći veće povjerenje u ponašanje vaše aplikacije u različitim scenarijima i regijama. Prihvaćanje ovih obrazaca osnažuje razvojne timove širom svijeta da isporuče izvanredna korisnička iskustva.

Počnite uključivati ove napredne tehnike u svoj radni proces već danas kako biste podigli svoje prakse JavaScript testiranja na višu razinu.

Jest: Napredni obrasci testiranja za robustan softver | MLOG