Čeština

Osvojte si pokročilé vzory testování v Jestu pro spolehlivější a udržitelnější software. Prozkoumejte techniky jako mocking, snapshot testování, vlastní matchery a další pro globální vývojářské týmy.

Jest: Pokročilé vzory testování pro robustní software

V dnešním rychle se vyvíjejícím světě softwarového vývoje je zajištění spolehlivosti a stability vaší kódové základny prvořadé. Ačkoli se Jest stal de facto standardem pro testování JavaScriptu, posun za hranice základních unit testů odemyká novou úroveň důvěry ve vaše aplikace. Tento příspěvek se ponoří do pokročilých vzorů testování v Jestu, které jsou nezbytné pro tvorbu robustního softwaru a jsou určeny pro globální publikum vývojářů.

Proč jít nad rámec základních unit testů?

Základní unit testy ověřují jednotlivé komponenty v izolaci. Skutečné aplikace jsou však komplexní systémy, kde komponenty vzájemně interagují. Pokročilé testovací vzory řeší tyto složitosti tím, že nám umožňují:

Zdokonalení mockování a spyů

Mockování je klíčové pro izolaci testované jednotky nahrazením jejích závislostí kontrolovanými náhradami. Jest k tomu poskytuje mocné nástroje:

jest.fn(): Základ mocků a spyů

jest.fn() vytváří mock funkci. Můžete sledovat její volání, argumenty a návratové hodnoty. Toto je stavební kámen pro sofistikovanější strategie mockování.

Příklad: Sledování volání funkce

// component.js
export const fetchData = () => {
  // Simuluje volání API
  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(): Pozorování bez nahrazení

jest.spyOn() vám umožňuje pozorovat volání metody na existujícím objektu, aniž byste nutně nahradili její implementaci. V případě potřeby můžete implementaci také mockovat.

Příklad: Sledování metody modulu

// 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 úkolu ...
  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(); // Důležité je obnovit původní implementaci
});

Mockování importů modulů

Možnosti mockování modulů v Jestu jsou rozsáhlé. Můžete mockovat celé moduly nebo specifické exporty.

Příklad: Mockování externího API klienta

// 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';

// Mockujeme celý modul api
jest.mock('./api');

test('should get full name using mocked API', async () => {
  // Mockujeme konkrétní funkci z mockovaného modulu
  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);
});

Automatické vs. manuální mockování

Jest automaticky mockuje moduly Node.js. Pro ES moduly nebo vlastní moduly můžete potřebovat jest.mock(). Pro větší kontrolu můžete vytvořit adresáře __mocks__.

Mock implementace

Pro své mocky můžete poskytnout vlastní implementace.

Příklad: Mockování s vlastní implementací

// 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';

// Mockujeme celý modul math
jest.mock('./math');

test('should perform addition using mocked math add', () => {
  // Poskytneme mock implementaci pro funkci 'add'
  math.add.mockImplementation((a, b) => a + b + 10); // Přičte 10 k výsledku
  math.subtract.mockReturnValue(5); // Mockujeme také odečítání

  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 testování: Zachování UI a konfigurace

Snapshot testy jsou mocnou funkcí pro zachycení výstupu vašich komponent nebo konfigurací. Jsou zvláště užitečné pro testování UI nebo ověřování složitých datových struktur.

Jak funguje snapshot testování

Při prvním spuštění snapshot testu vytvoří Jest soubor .snap obsahující serializovanou reprezentaci testované hodnoty. Při dalších spuštěních Jest porovná aktuální výstup s uloženým snapshotem. Pokud se liší, test selže, což vás upozorní na neúmyslné změny. To je neocenitelné pro odhalování regresí v UI komponentách v různých regionech nebo lokalizacích.

Příklad: Snapshotování React komponenty

Za předpokladu, že máte 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'; // Pro snapshoty React komponent
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'); // Pojmenovaný snapshot
});

Po spuštění testů Jest vytvoří soubor UserProfile.test.js.snap. Když komponentu aktualizujete, budete muset zkontrolovat změny a případně aktualizovat snapshot spuštěním Jestu s příznakem --updateSnapshot nebo -u.

Osvědčené postupy pro snapshot testování

Vlastní matchery: Zlepšení čitelnosti testů

Vestavěné matchery v Jestu jsou rozsáhlé, ale někdy potřebujete ověřit specifické podmínky, které nejsou pokryty. Vlastní matchery vám umožní vytvořit vlastní logiku pro ověřování, čímž se vaše testy stanou expresivnějšími a čitelnějšími.

Vytváření vlastních matcherů

Můžete rozšířit objekt expect v Jestu o vlastní matchery.

Příklad: Kontrola platného formátu e-mailu

Ve vašem konfiguračním souboru Jestu (např. jest.setup.js, nakonfigurovaném v 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,
      };
    }
  },
});

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

Ve vašem testovacím souboru:

// 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();
});

Výhody vlastních matcherů

Testování asynchronních operací

JavaScript je silně asynchronní. Jest poskytuje vynikající podporu pro testování promises a async/await.

Použití async/await

Toto je moderní a nejčitelnější způsob testování asynchronního kódu.

Příklad: Testování asynchronní funkce

// dataService.js
export const fetchUserData = async (userId) => {
  // Simuluje načtení dat po prodlevě
  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');
});

Použití .resolves a .rejects

Tyto matchery zjednodušují testování vyřešení a zamítnutí promises.

Příklad: Použití .resolves/.rejects

// dataService.test.js (pokračování)

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');
});

Práce s časovači

Pro funkce, které používají setTimeout nebo setInterval, Jest poskytuje kontrolu nad časovači.

Příklad: Ovládání časovačů

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

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

jest.useFakeTimers(); // Povolení falešných časovačů

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

  // Posunout časovače o 1000 ms
  jest.advanceTimersByTime(1000);

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

// Obnovení reálných časovačů, pokud jsou potřeba jinde
jest.useRealTimers();

Organizace a struktura testů

Jak vaše sada testů roste, organizace se stává pro udržitelnost klíčovou.

Bloky Describe a It

Používejte describe pro seskupení souvisejících testů a it (nebo test) pro jednotlivé testovací případy. Tato struktura odráží modularitu aplikace.

Příklad: Strukturované testy

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

  beforeEach(() => {
    // Nastavení mocků nebo instancí služby před každým testem
    authService = require('./authService');
    jest.spyOn(authService, 'login').mockImplementation(() => Promise.resolve({ token: 'fake_token' }));
  });

  afterEach(() => {
    // Vyčištění mocků
    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();
      // ... další assertions ...
    });

    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 () => {
      // Testování logiky odhlášení...
    });
  });
});

Hooks pro nastavení a úklid (Setup a Teardown)

Tyto hooks jsou nezbytné pro nastavení mock dat, databázových připojení nebo čištění zdrojů mezi testy.

Testování pro globální publikum

Při vývoji aplikací pro globální publikum se úvahy o testování rozšiřují:

Internacionalizace (i18n) a lokalizace (l10n)

Zajistěte, aby se vaše UI a zprávy správně přizpůsobovaly různým jazykům a regionálním formátům.

Příklad: Testování lokalizovaného formátování data

// 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. listopadu 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');
});

Povědomí o časových zónách

Testujte, jak vaše aplikace zpracovává různé časové zóny, zejména u funkcí jako je plánování nebo aktualizace v reálném čase. Mockování systémových hodin nebo používání knihoven, které abstrahují časové zóny, může být přínosné.

Kulturní nuance v datech

Zvažte, jak mohou být čísla, měny a další reprezentace dat vnímány nebo očekávány odlišně v různých kulturách. Zde mohou být obzvláště užitečné vlastní matchery.

Pokročilé techniky a strategie

Vývoj řízený testy (TDD) a vývoj řízený chováním (BDD)

Jest se dobře hodí k metodologiím TDD (Red-Green-Refactor) a BDD (Given-When-Then). Pište testy, které popisují požadované chování, ještě před napsáním implementačního kódu. Tím zajistíte, že je kód psán s ohledem na testovatelnost od samého začátku.

Integrační testování s Jestem

Ačkoli Jest vyniká v unit testech, lze jej použít i pro integrační testy. Mockování menšího počtu závislostí nebo použití nástrojů, jako je volba runInBand v Jestu, může pomoci.

Příklad: Testování interakce s API (zjednodušeně)

// 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 (Integrační test)
import axios from 'axios';
import { createProduct } from './apiService';

// Mockování axiosu pro integrační testy pro kontrolu síťové vrstvy
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);
});

Paralelismus a konfigurace

Jest může spouštět testy paralelně pro zrychlení provádění. Nakonfigurujte to ve svém jest.config.js. Například nastavení maxWorkers řídí počet paralelních procesů.

Zprávy o pokrytí (Coverage Reports)

Používejte vestavěné reportování pokrytí v Jestu k identifikaci částí vaší kódové základny, které nejsou testovány. Spusťte testy s --coverage pro generování podrobných zpráv.

jest --coverage

Kontrola zpráv o pokrytí pomáhá zajistit, že vaše pokročilé testovací vzory efektivně pokrývají kritickou logiku, včetně kódu pro internacionalizaci a lokalizaci.

Závěr

Osvojení si pokročilých testovacích vzorů v Jestu je významným krokem k budování spolehlivého, udržitelného a vysoce kvalitního softwaru pro globální publikum. Efektivním využíváním mockování, snapshot testování, vlastních matcherů a technik pro asynchronní testování můžete zvýšit robustnost vaší testovací sady a získat větší důvěru v chování vaší aplikace v různých scénářích a regionech. Přijetí těchto vzorů umožňuje vývojářským týmům po celém světě poskytovat výjimečné uživatelské zážitky.

Začněte začleňovat tyto pokročilé techniky do svého pracovního postupu ještě dnes, abyste pozvedli své praktiky testování JavaScriptu.