React Native / Тест бичих (Jest + RNTL)

Тест бичих (Jest + RNTL)

"Энэ код ажиллаж байна" гэдгийг хэрхэн баталгаажуулах вэ? Гараар туршихад цаг их зарцуулдаг, өөрчлөлт хийх бүрт дахин дахин шалгах хэрэгтэй болдог. Автоматжуулсан тест нь кодыг тань хурдан, найдвартай шалгадаг — нэг удаа бичихэд тань байнга ажиллаж байдаг. Эхлэгчдэд хэцүү санагдаж болох ч үндсэн тестийг бичиж сурахад маш хурдан болно.

Jest тохируулга

Expo апп үүсгэхэд Jest автоматаар суулгагддаг. @testing-library/react-native нэмэн суулгана:

bash
npx expo install jest-expo @testing-library/react-native @testing-library/jest-native

package.json-д Jest тохируулга нэмнэ:

json
{
  "scripts": {
    "test": "jest",
    "test:watch": "jest --watch"
  },
  "jest": {
    "preset": "jest-expo",
    "setupFilesAfterFramework": ["@testing-library/jest-native/extend-expect"],
    "transformIgnorePatterns": [
      "node_modules/(?!((jest-)?react-native|@react-native(-community)?)|expo(nent)?|@expo(nent)?/.*|@expo-google-fonts/.*|react-navigation|@react-navigation/.*|@unimodules/.*|unimodules|sentry-expo|native-base|react-native-svg)"
    ]
  }
}

Эхний тест — component шалгах

__tests__ хавтас үүсгэж тест файлуудаа тэнд хадгална. Нэрлэх дүрэм: ComponentName.test.tsx:

typescript
// __tests__/AppButton.test.tsx
import React from 'react';
import { render, fireEvent, screen } from '@testing-library/react-native';
import { AppButton } from '../components/AppButton';

describe('AppButton', () => {
  it('label зөв харуулна', () => {
    render(<AppButton label="Нэвтрэх" onPress={() => {}} />);

    // Текст дэлгэцэнд байгаа эсэхийг шалгана
    expect(screen.getByText('Нэвтрэх')).toBeTruthy();
  });

  it('дарахад onPress дуудагдана', () => {
    // jest.fn() нь функц дуудагдсан эсэхийг хянах "spy"
    const mockPress = jest.fn();
    render(<AppButton label="Дарах" onPress={mockPress} />);

    fireEvent.press(screen.getByText('Дарах'));

    expect(mockPress).toHaveBeenCalledTimes(1);
  });

  it('disabled үед дарахад onPress дуудагдахгүй', () => {
    const mockPress = jest.fn();
    render(<AppButton label="Идэвхгүй" onPress={mockPress} disabled />);

    fireEvent.press(screen.getByText('Идэвхгүй'));

    expect(mockPress).not.toHaveBeenCalled();
  });
});

Terminal дээр npx jest ажиллуулахад тест гүйж үр дүн гарна:

код
✓ AppButton › label зөв харуулна (23ms)
✓ AppButton › дарахад onPress дуудагдана (8ms)
✓ AppButton › disabled үед дарахад onPress дуудагдахгүй (5ms)

Tests: 3 passed, 3 total

Hook тестлэх

Custom hook-ийг тестлэхэд renderHook ашиглана:

typescript
// __tests__/useCounter.test.ts
import { renderHook, act } from "@testing-library/react-native";

// Тестлэх hook
function useCounter(initial = 0) {
  const [count, setCount] = React.useState(initial);
  const increment = () => setCount((c) => c + 1);
  const decrement = () => setCount((c) => c - 1);
  const reset = () => setCount(initial);
  return { count, increment, decrement, reset };
}

describe("useCounter", () => {
  it("эхний утга зөв байна", () => {
    const { result } = renderHook(() => useCounter(5));
    expect(result.current.count).toBe(5);
  });

  it("increment ажиллана", () => {
    const { result } = renderHook(() => useCounter());

    act(() => {
      result.current.increment();
    });

    expect(result.current.count).toBe(1);
  });

  it("reset анхны утгад буцна", () => {
    const { result } = renderHook(() => useCounter(10));

    act(() => {
      result.current.increment();
      result.current.increment();
      result.current.reset();
    });

    expect(result.current.count).toBe(10);
  });
});

Async тест — fetch шалгах

API дуудлагатай код тестлэхэд mock ашиглана. Жинхэнэ сервер рүү хандахгүйгээр тестийг гүйцэтгэнэ:

typescript
// __tests__/LessonList.test.tsx
import React from 'react';
import { render, screen, waitFor } from '@testing-library/react-native';
import { LessonList } from '../components/LessonList';

// fetch-г mock болгоно — жинхэнэ сервер хэрэггүй
global.fetch = jest.fn();

const mockLessons = [
  { id: '1', title: 'React Native гэж юу вэ?', order: 1 },
  { id: '2', title: 'Node.js суулгах', order: 2 },
];

beforeEach(() => {
  (fetch as jest.Mock).mockResolvedValue({
    ok: true,
    json: async () => mockLessons,
  });
});

afterEach(() => {
  jest.clearAllMocks();
});

describe('LessonList', () => {
  it('ачаалж байхад loading харуулна', () => {
    render(<LessonList courseSlug="react-native" />);
    expect(screen.getByText('Ачааллаж байна...')).toBeTruthy();
  });

  it('хичээлүүдийг зөв харуулна', async () => {
    render(<LessonList courseSlug="react-native" />);

    // Async дуусахыг хүлээнэ
    await waitFor(() => {
      expect(screen.getByText('React Native гэж юу вэ?')).toBeTruthy();
      expect(screen.getByText('Node.js суулгах')).toBeTruthy();
    });
  });

  it('алдаа гарахад error харуулна', async () => {
    (fetch as jest.Mock).mockRejectedValue(new Error('Сүлжээний алдаа'));

    render(<LessonList courseSlug="react-native" />);

    await waitFor(() => {
      expect(screen.getByText('Сүлжээний алдаа')).toBeTruthy();
    });
  });
});

Тест бичих нь нэмэлт ажил мэт санагдаж болох ч ашиглах тусам "яагаад эрт сураагүй юм бэ" гэж бодох болно. Өчүүхэн тестүүдийг байнга бичих дадал болгоорой — том апп бүтээхдээ маш их тустай.

Дараагийн хичээлд:

EAS Build тохируулга — Expo Application Services ашиглан аппаа жинхэнэ .apk болон .ipa файл болгон build хийх, App Store / Play Store-д нийтлэхэд бэлдэх талаар сурна.