Bahasa Indonesia

Kuasai pola pengujian Jest tingkat lanjut untuk membangun perangkat lunak yang andal. Jelajahi mocking, snapshot testing, custom matchers, dan lainnya.

Jest: Pola Pengujian Tingkat Lanjut untuk Perangkat Lunak yang Tangguh

Dalam lanskap pengembangan perangkat lunak yang serba cepat saat ini, memastikan keandalan dan stabilitas basis kode Anda adalah hal yang terpenting. Meskipun Jest telah menjadi standar de facto untuk pengujian JavaScript, melampaui pengujian unit dasar akan membuka tingkat kepercayaan baru pada aplikasi Anda. Postingan ini membahas pola pengujian Jest tingkat lanjut yang penting untuk membangun perangkat lunak yang tangguh, yang diperuntukkan bagi audiens pengembang global.

Mengapa Melampaui Pengujian Unit Dasar?

Pengujian unit dasar memverifikasi komponen individual secara terpisah. Namun, aplikasi di dunia nyata adalah sistem kompleks tempat komponen berinteraksi. Pola pengujian tingkat lanjut mengatasi kompleksitas ini dengan memungkinkan kita untuk:

Menguasai Mocking dan Spies

Mocking sangat penting untuk mengisolasi unit yang diuji dengan mengganti dependensinya dengan pengganti yang terkontrol. Jest menyediakan alat yang kuat untuk ini:

jest.fn(): Fondasi dari Mock dan Spy

jest.fn() membuat fungsi tiruan (mock function). Anda dapat melacak pemanggilan, argumen, dan nilai kembaliannya. Ini adalah blok bangunan untuk strategi mocking yang lebih canggih.

Contoh: Melacak Pemanggilan Fungsi

// component.js
export const fetchData = () => {
  // Mensimulasikan panggilan 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(): Mengamati Tanpa Mengganti

jest.spyOn() memungkinkan Anda untuk mengamati pemanggilan metode pada objek yang ada tanpa harus mengganti implementasinya. Anda juga dapat meniru implementasinya jika diperlukan.

Contoh: Memata-matai Metode Modul

// 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 tugas ...
  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(); // Penting untuk mengembalikan implementasi asli
});

Mocking Impor Modul

Kemampuan mocking modul Jest sangat luas. Anda dapat meniru seluruh modul atau ekspor tertentu.

Contoh: Mocking Klien API Eksternal

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

// Mock seluruh modul api
jest.mock('./api');

test('should get full name using mocked API', async () => {
  // Mock fungsi spesifik dari modul yang di-mock
  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);
});

Mocking Otomatis vs. Mocking Manual

Jest secara otomatis meniru (mock) modul Node.js. Untuk modul ES atau modul kustom, Anda mungkin memerlukan jest.mock(). Untuk kontrol lebih, Anda dapat membuat direktori __mocks__.

Implementasi Mock

Anda dapat menyediakan implementasi kustom untuk mock Anda.

Contoh: Mocking dengan Implementasi Kustom

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

// Mock seluruh modul math
jest.mock('./math');

test('should perform addition using mocked math add', () => {
  // Sediakan implementasi mock untuk fungsi 'add'
  math.add.mockImplementation((a, b) => a + b + 10); // Tambahkan 10 ke hasilnya
  math.subtract.mockReturnValue(5); // Mock juga fungsi 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);
});

Pengujian Snapshot: Menjaga UI dan Konfigurasi

Pengujian snapshot adalah fitur canggih untuk menangkap output dari komponen atau konfigurasi Anda. Fitur ini sangat berguna untuk pengujian UI atau memverifikasi struktur data yang kompleks.

Cara Kerja Pengujian Snapshot

Pertama kali pengujian snapshot dijalankan, Jest membuat file .snap yang berisi representasi serial dari nilai yang diuji. Pada proses selanjutnya, Jest membandingkan output saat ini dengan snapshot yang tersimpan. Jika berbeda, pengujian akan gagal, memberitahu Anda tentang perubahan yang tidak diinginkan. Ini sangat berharga untuk mendeteksi regresi pada komponen UI di berbagai wilayah atau lokal.

Contoh: Membuat Snapshot Komponen React

Anggap Anda memiliki komponen React:

// 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'; // Untuk snapshot komponen React
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'); // Snapshot bernama
});

Setelah menjalankan pengujian, Jest akan membuat file UserProfile.test.js.snap. Saat Anda memperbarui komponen, Anda perlu meninjau perubahan dan berpotensi memperbarui snapshot dengan menjalankan Jest dengan flag --updateSnapshot atau -u.

Praktik Terbaik untuk Pengujian Snapshot

Custom Matcher: Meningkatkan Keterbacaan Tes

Matcher bawaan Jest sangat banyak, tetapi terkadang Anda perlu menegaskan kondisi spesifik yang tidak tercakup. Custom matcher memungkinkan Anda membuat logika asersi sendiri, membuat pengujian Anda lebih ekspresif dan mudah dibaca.

Membuat Custom Matcher

Anda dapat memperluas objek expect Jest dengan matcher Anda sendiri.

Contoh: Memeriksa Format Email yang Valid

Di file penyiapan Jest Anda (misalnya, jest.setup.js, dikonfigurasi di jest.config.js):

// jest.setup.js

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

    if (pass) {
      return {
        message: () => `mengharapkan ${received} bukan email yang valid`,
        pass: true,
      };
    } else {
      return {
        message: () => `mengharapkan ${received} adalah email yang valid`,
        pass: false,
      };
    }
  },
});

// Di jest.config.js Anda
// module.exports = { setupFilesAfterEnv: ['/jest.setup.js'] };

Di file pengujian Anda:

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

Manfaat Custom Matcher

Menguji Operasi Asinkron

JavaScript sangat bergantung pada operasi asinkron. Jest menyediakan dukungan yang sangat baik untuk menguji promise dan async/await.

Menggunakan async/await

Ini adalah cara modern dan paling mudah dibaca untuk menguji kode asinkron.

Contoh: Menguji Fungsi Asinkron

// dataService.js
export const fetchUserData = async (userId) => {
  // Mensimulasikan pengambilan data setelah jeda
  await new Promise(resolve => setTimeout(resolve, 50));
  if (userId === 1) {
    return { id: 1, name: 'Alice' };
  } else {
    throw new Error('Pengguna tidak ditemukan');
  }
};

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

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

test('melemparkan galat untuk pengguna yang tidak ada', async () => {
  await expect(fetchUserData(2)).rejects.toThrow('Pengguna tidak ditemukan');
});

Menggunakan .resolves dan .rejects

Matcher ini menyederhanakan pengujian resolusi dan penolakan promise.

Contoh: Menggunakan .resolves/.rejects

// dataService.test.js (lanjutan)

test('mengambil data pengguna dengan .resolves', () => {
  return expect(fetchUserData(1)).resolves.toEqual({ id: 1, name: 'Alice' });
});

test('melemparkan galat untuk pengguna yang tidak ada dengan .rejects', () => {
  return expect(fetchUserData(2)).rejects.toThrow('Pengguna tidak ditemukan');
});

Menangani Timer

Untuk fungsi yang menggunakan setTimeout atau setInterval, Jest menyediakan kontrol timer.

Contoh: Mengontrol Timer

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

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

jest.useFakeTimers(); // Aktifkan timer palsu

test('menyapa setelah jeda', () => {
  const mockCallback = jest.fn();
  greetAfterDelay('World', mockCallback);

  // Majukan timer sebesar 1000ms
  jest.advanceTimersByTime(1000);

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

// Kembalikan timer asli jika diperlukan di tempat lain
jest.useRealTimers();

Organisasi dan Struktur Tes

Seiring bertambahnya rangkaian pengujian Anda, organisasi menjadi sangat penting untuk kemudahan pemeliharaan.

Blok Describe dan It

Gunakan describe untuk mengelompokkan pengujian terkait dan it (atau test) untuk kasus pengujian individual. Struktur ini mencerminkan modularitas aplikasi.

Contoh: Pengujian Terstruktur

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

  beforeEach(() => {
    // Siapkan mock atau instance layanan sebelum setiap tes
    authService = require('./authService');
    jest.spyOn(authService, 'login').mockImplementation(() => Promise.resolve({ token: 'fake_token' }));
  });

  afterEach(() => {
    // Bersihkan 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();
      // ... asersi lainnya ...
    });

    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 () => {
      // Uji logika logout...
    });
  });
});

Hook Setup dan Teardown

Hook ini penting untuk menyiapkan data mock, koneksi basis data, atau membersihkan sumber daya di antara pengujian.

Pengujian untuk Audiens Global

Saat mengembangkan aplikasi untuk audiens global, pertimbangan pengujian meluas:

Internasionalisasi (i18n) dan Lokalisasi (l10n)

Pastikan UI dan pesan Anda beradaptasi dengan benar ke berbagai bahasa dan format regional.

Contoh: Menguji pemformatan tanggal yang dilokalkan

// 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('memformat tanggal dengan benar untuk lokal AS', () => {
  const date = new Date(2023, 10, 15); // 15 November 2023
  expect(formatLocalizedDate(date, 'en-US')).toBe('11/15/2023');
});

test('memformat tanggal dengan benar untuk lokal Jerman', () => {
  const date = new Date(2023, 10, 15);
  expect(formatLocalizedDate(date, 'de-DE')).toBe('15.11.2023');
});

Kewaspadaan Zona Waktu

Uji bagaimana aplikasi Anda menangani zona waktu yang berbeda, terutama untuk fitur seperti penjadwalan atau pembaruan waktu nyata. Melakukan mock pada jam sistem atau menggunakan pustaka yang mengabstraksikan zona waktu bisa bermanfaat.

Nuansa Budaya dalam Data

Pertimbangkan bagaimana angka, mata uang, dan representasi data lainnya mungkin dirasakan atau diharapkan secara berbeda di berbagai budaya. Custom matcher dapat sangat berguna di sini.

Teknik dan Strategi Tingkat Lanjut

Test-Driven Development (TDD) dan Behavior-Driven Development (BDD)

Jest selaras dengan metodologi TDD (Red-Green-Refactor) dan BDD (Given-When-Then). Tulis pengujian yang menggambarkan perilaku yang diinginkan sebelum menulis kode implementasi. Ini memastikan bahwa kode ditulis dengan mempertimbangkan kemampuan pengujian sejak awal.

Pengujian Integrasi dengan Jest

Meskipun Jest unggul dalam pengujian unit, ia juga dapat digunakan untuk pengujian integrasi. Melakukan mock lebih sedikit dependensi atau menggunakan alat seperti opsi runInBand Jest dapat membantu.

Contoh: Menguji Interaksi API (disederhanakan)

// 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 (Uji Integrasi)
import axios from 'axios';
import { createProduct } from './apiService';

// Mock axios untuk pengujian integrasi guna mengontrol lapisan jaringan
jest.mock('axios');

test('membuat produk melalui 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);
});

Paralelisme dan Konfigurasi

Jest dapat menjalankan pengujian secara paralel untuk mempercepat eksekusi. Konfigurasikan ini di jest.config.js Anda. Misalnya, mengatur maxWorkers mengontrol jumlah proses paralel.

Laporan Cakupan

Gunakan pelaporan cakupan bawaan Jest untuk mengidentifikasi bagian-bagian dari basis kode Anda yang tidak diuji. Jalankan pengujian dengan --coverage untuk menghasilkan laporan terperinci.

jest --coverage

Meninjau laporan cakupan membantu memastikan bahwa pola pengujian tingkat lanjut Anda secara efektif mencakup logika kritis, termasuk jalur kode internasionalisasi dan lokalisasi.

Kesimpulan

Menguasai pola pengujian Jest tingkat lanjut adalah langkah signifikan menuju pembangunan perangkat lunak yang andal, mudah dikelola, dan berkualitas tinggi untuk audiens global. Dengan memanfaatkan mocking, pengujian snapshot, custom matcher, dan teknik pengujian asinkron secara efektif, Anda dapat meningkatkan ketangguhan rangkaian pengujian Anda dan mendapatkan kepercayaan yang lebih besar terhadap perilaku aplikasi Anda di berbagai skenario dan wilayah. Menerapkan pola-pola ini memberdayakan tim pengembangan di seluruh dunia untuk memberikan pengalaman pengguna yang luar biasa.

Mulai gabungkan teknik-teknik canggih ini ke dalam alur kerja Anda hari ini untuk meningkatkan praktik pengujian JavaScript Anda.