Strategi Testing untuk Web Apps — Panduan Quality Assurance Komprehensif

Strategi Testing untuk Web Apps — Panduan Quality Assurance Komprehensif

30/10/2025 Testing By Tech Writers
TestingJestCypressQAAutomationTest CoveragePengembangan WebBest Practices

Pengenalan: Membangun Kepercayaan Melalui Testing

Testing adalah landasan pengembangan software profesional. Strategi testing komprehensif memastikan kualitas kode, mencegah regresi, menyediakan dokumentasi melalui test, dan pada akhirnya menghemat waktu dan uang dengan menangkap bug sebelum mereka mencapai production. Di tahun 2026, testing tidak lagi opsional—ini adalah essential untuk aplikasi web yang serius.

Panduan komprehensif ini mencakup lanskap testing lengkap untuk aplikasi web, dari unit test hingga end-to-end testing, membantu Kamu membangun testing pyramid yang robust untuk menangkap masalah di setiap level.

Daftar Isi

Mengapa Testing Penting

Testing bukan hanya tentang menangkap bug—ini adalah praktik fundamental yang meningkatkan kualitas kode, mengurangi biaya, dan memberikan kepercayaan dalam deployment. Memahami mengapa testing penting membantu Kamu berkomitmen untuk membangun budaya testing di organisasi Kamu.

Manfaat Comprehensive Testing

Quality Assurance: Test memastikan bahwa kode berfungsi seperti yang diharapkan dan menangkap regresi sejak dini dalam siklus pengembangan. Basis kode yang diuji dengan baik secara signifikan lebih dapat diandalkan dan mudah dipelihara.

Pengembangan Lebih Cepat: Meskipun menulis test membutuhkan waktu awal, mereka menghemat waktu signifikan selama refactoring dan debugging. Test bertindak sebagai dokumentasi dan safety net untuk perubahan.

Pengurangan Biaya: Memperbaiki bug di production secara eksponensial lebih mahal daripada menangkapnya dalam pengembangan. Test mengurangi biaya maintenance dan support.

Kepercayaan: Test komprehensif memberikan developer kepercayaan untuk refactor, optimize, dan menambah fitur tanpa takut merusak functionality yang sudah ada.

Dokumentasi: Test berfungsi sebagai dokumentasi executable yang menunjukkan bagaimana kode harus digunakan dan perilaku apa yang diharapkan.

Testing Pyramid

Testing pyramid mengilustrasikan distribusi ideal test di berbagai level. Prinsip fundamental adalah memiliki banyak unit test yang cepat, sejumlah moderate integration test, dan set yang lebih kecil dari slow end-to-end test.

        E2E Tests (10%)
       /              \
      Integration Tests (30%)
     /                        \
   Unit Tests (60%)

Rasio Pyramid dan Reasoning

Unit Tests (60%): Cepat, terisolasi, fokus pada fungsi dan komponen individual. Mudah ditulis dan dipelihara. Harus membentuk dasar dari testing pyramid Kamu.

Integration Tests (30%): Test bagaimana multiple komponen atau modules bekerja bersama. Lebih lambat daripada unit test tetapi lebih cepat daripada E2E. Menangkap interaction bug yang unit test lewatkan.

E2E Tests (10%): Test complete user workflows di real browser environment. Paling lambat tetapi paling representative dari real user experience. Gunakan sparingly untuk critical path.

Unit Testing dengan Jest

Jest adalah testing framework standar industri untuk aplikasi JavaScript dan React. Ini menyediakan semua yang diperlukan untuk unit testing: test runner, assertion library, mocking utilities, dan coverage reporting.

Mengatur Jest

Instal Jest dan dependencies yang diperlukan di project Kamu:

npm install --save-dev jest @babel/preset-env

Konfigurasikan Jest di package.json Kamu:

{
  "jest": {
    "testEnvironment": "jsdom",
    "setupFilesAfterEnv": ["<rootDir>/src/setupTests.js"],
    "collectCoverageFrom": [
      "src/**/*.{js,jsx}",
      "!src/index.js",
      "!src/reportWebVitals.js"
    ],
    "coverageThreshold": {
      "global": {
        "branches": 70,
        "functions": 70,
        "lines": 70,
        "statements": 70
      }
    }
  }
}

Unit test mengikuti pola Susun-Jalankan-Verifikasi: siapkan data test, jalankan kode yang ditest, dan verifikasi hasilnya.

// math.js
export function sum(a, b) {
  return a + b;
}

export function multiply(a, b) {
  return a * b;
}

// math.test.js
import { sum, multiply } from './math';

describe('Math utilities', () => {
  describe('sum function', () => {
    it('should add two positive numbers', () => {
      expect(sum(2, 3)).toBe(5);
    });

    it('should handle negative numbers', () => {
      expect(sum(-5, 3)).toBe(-2);
    });

    it('should handle zero', () => {
      expect(sum(0, 5)).toBe(5);
    });
  });

  describe('multiply function', () => {
    it('should multiply two numbers', () => {
      expect(multiply(3, 4)).toBe(12);
    });

    it('should return zero when multiplying by zero', () => {
      expect(multiply(5, 0)).toBe(0);
    });
  });
});

Menggunakan Matchers

Jest menyediakan matcher ekstensif untuk berbagai jenis assertion. Menggunakan matcher yang tepat membuat test lebih mudah dibaca dan memberikan pesan error yang lebih baik.

describe('Matchers Jest', () => {
  it('mendemonstrasikan berbagai matchers', () => {
    // Kesamaan
    expect(2 + 2).toBe(4);
    expect({ name: 'John' }).toEqual({ name: 'John' });
    
    // Kebenaran
    expect(null).toBeNull();
    expect(undefined).toBeUndefined();
    expect(true).toBeTruthy();
    expect(0).toBeFalsy();
    
    // Angka
    expect(3.14).toBeCloseTo(3.1, 1);
    expect(4).toBeGreaterThan(3);
    expect(3).toBeLessThanOrEqual(3);
    
    // String
    expect('JavaScript').toMatch(/Script/);
    expect('testing').toHaveLength(7);
    
    // Array dan koleksi
    expect([1, 2, 3]).toContain(2);
    expect({ name: 'John', age: 30 }).toHaveProperty('name');
  });
});

Mocking Functions

Mock menggantikan implementasi sebenarnya dengan test double, mengisolasi kode yang ditest. Penting untuk menguji fungsi dengan efek samping atau dependensi eksternal.

// userService.js
import { apiClient } from './apiClient';

export async function getUser(id) {
  return apiClient.get(`/users/${id}`);
}

// userService.test.js
import { getUser } from './userService';
import * as userService from './userService';

// Simulasi API client
jest.mock('./apiClient', () => ({
  apiClient: {
    get: jest.fn()
  }
}));

describe('userService', () => {
  beforeEach(() => {
    jest.clearAllMocks();
  });

  it('harus mengambil data user', async () => {
    const mockUser = { id: 1, name: 'John Doe' };
    const { apiClient } = require('./apiClient');
    apiClient.get.mockResolvedValue(mockUser);

    const result = await getUser(1);

    expect(apiClient.get).toHaveBeenCalledWith('/users/1');
    expect(result).toEqual(mockUser);
  });

  it('harus menangani error API', async () => {
    const { apiClient } = require('./apiClient');
    apiClient.get.mockRejectedValue(new Error('API Error'));

    await expect(getUser(1)).rejects.toThrow('API Error');
  });
});

Testing Komponen React

React Testing Library mendorong pengujian komponen dari perspektif pengguna daripada menguji detail implementasi. Ini menghasilkan test yang lebih kokoh yang sesuai dengan perilaku pengguna nyata.

Setup React Testing Library

npm install --save-dev @testing-library/react @testing-library/jest-dom

Testing Komponen Dasar

Test komponen harus fokus pada perilaku pengguna, bukan pada detail implementasi. Render komponen dan verifikasi apa yang dilihat dan dapat diinteraksikan pengguna.

// Button.jsx
export function Button({ label, onClick, disabled = false }) {
  return (
    <button onClick={onClick} disabled={disabled}>
      {label}
    </button>
  );
}

// Button.test.jsx
import { render, screen, fireEvent } from '@testing-library/react';
import { Button } from './Button';

describe('Button Component', () => {
  it('seharusnya render dengan label yang benar', () => {
    render(<Button label="Click me" onClick={() => {}} />);
    
    expect(screen.getByText('Click me')).toBeInTheDocument();
  });

  it('seharusnya memanggil onClick handler ketika diklik', () => {
    const handleClick = jest.fn();
    render(<Button label="Click me" onClick={handleClick} />);
    
    fireEvent.click(screen.getByText('Click me'));
    
    expect(handleClick).toHaveBeenCalledTimes(1);
  });

  it('seharusnya disabled ketika disabled prop adalah true', () => {
    render(<Button label="Disabled" onClick={() => {}} disabled={true} />);
    
    expect(screen.getByText('Disabled')).toBeDisabled();
  });
});

Testing Komponen State dan Effects

Testing komponen yang mengelola state dan efek samping memerlukan pemahaman tentang utilitas async dari React Testing Library.

// UserForm.jsx
import { useState, useEffect } from 'react';

export function UserForm({ onSubmit }) {
  const [formData, setFormData] = useState({ name: '', email: '' });
  const [errors, setErrors] = useState({});

  const handleChange = (e) => {
    const { name, value } = e.target;
    setFormData(prev => ({ ...prev, [name]: value }));
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    const newErrors = {};
    
    if (!formData.name) newErrors.name = 'Name is required';
    if (!formData.email) newErrors.email = 'Email is required';
    
    if (Object.keys(newErrors).length > 0) {
      setErrors(newErrors);
      return;
    }
    
    onSubmit(formData);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        name="name"
        value={formData.name}
        onChange={handleChange}
        placeholder="Name"
      />
      {errors.name && <span>{errors.name}</span>}
      
      <input
        name="email"
        value={formData.email}
        onChange={handleChange}
        placeholder="Email"
      />
      {errors.email && <span>{errors.email}</span>}
      
      <button type="submit">Submit</button>
    </form>
  );
}

// UserForm.test.jsx
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { UserForm } from './UserForm';

describe('UserForm Component', () => {
  it('seharusnya menampilkan validation errors', async () => {
    render(<UserForm onSubmit={() => {}} />);
    
    fireEvent.click(screen.getByText('Submit'));
    
    expect(await screen.findByText('Name is required')).toBeInTheDocument();
    expect(screen.getByText('Email is required')).toBeInTheDocument();
  });

  it('seharusnya submit form dengan data yang valid', async () => {
    const handleSubmit = jest.fn();
    render(<UserForm onSubmit={handleSubmit} />);
    
    await userEvent.type(screen.getByPlaceholderText('Name'), 'John Doe');
    await userEvent.type(screen.getByPlaceholderText('Email'), '[email protected]');
    
    fireEvent.click(screen.getByText('Submit'));
    
    await waitFor(() => {
      expect(handleSubmit).toHaveBeenCalledWith({
        name: 'John Doe',
        email: '[email protected]'
      });
    });
  });
});

Integration Testing

Tes integrasi memverifikasi bahwa beberapa komponen dan modul bekerja dengan benar bersama-sama. Mereka lebih lambat daripada unit test tetapi lebih cepat daripada E2E test, menjadikannya ideal untuk pengujian alur fitur.

Testing Multiple Components

Tes integrasi harus menguji fitur, bukan komponen individual. Fokus pada perilaku yang terlihat oleh pengguna dan bagaimana berbagai bagian berinteraksi.

// UserList.jsx - Parent component
import { useState, useEffect } from 'react';
import { UserCard } from './UserCard';

export function UserList() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    fetch('/api/users')
      .then(res => res.json())
      .then(data => {
        setUsers(data);
        setLoading(false);
      })
      .catch(err => {
        setError(err.message);
        setLoading(false);
      });
  }, []);

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;

  return (
    <div>
      {users.map(user => (
        <UserCard key={user.id} user={user} />
      ))}
    </div>
  );
}

// UserList.integration.test.jsx
import { render, screen, waitFor } from '@testing-library/react';
import { UserList } from './UserList';

describe('UserList Integration', () => {
  beforeEach(() => {
    global.fetch = jest.fn();
  });

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

  it('should fetch and display users', async () => {
    const mockUsers = [
      { id: 1, name: 'John Doe', email: '[email protected]' },
      { id: 2, name: 'Jane Smith', email: '[email protected]' }
    ];

    global.fetch.mockResolvedValueOnce({
      json: async () => mockUsers
    });

    render(<UserList />);

    await waitFor(() => {
      expect(screen.getByText('John Doe')).toBeInTheDocument();
      expect(screen.getByText('Jane Smith')).toBeInTheDocument();
    });
  });

  it('should display error message on fetch failure', async () => {
    global.fetch.mockRejectedValueOnce(new Error('Network error'));

    render(<UserList />);

    await waitFor(() => {
      expect(screen.getByText(/Error: Network error/)).toBeInTheDocument();
    });
  });
});

End-to-End Testing dengan Cypress

Cypress menyediakan kerangka kerja yang kuat untuk menguji alur kerja pengguna lengkap di peramban asli. Test ditulis dalam JavaScript dan berjalan dalam konteks peramban, membuat debug menjadi mudah.

Cypress Setup dan Test Pertama

Instal dan inisialisasi Cypress:

npm install --save-dev cypress
npx cypress open

Tulis E2E test pertama Kamu:

// cypress/e2e/login.cy.js
describe('Login Flow', () => {
  beforeEach(() => {
    cy.visit('http://localhost:3000/login');
  });

  it('seharusnya berhasil login dengan credentials yang valid', () => {
    // Enter email
    cy.get('[data-testid=email-input]').type('[email protected]');
    
    // Enter password
    cy.get('[data-testid=password-input]').type('password123');
    
    // Click login button
    cy.get('[data-testid=login-button]').click();
    
    // Verify redirect to dashboard
    cy.url().should('include', '/dashboard');
    cy.get('[data-testid=welcome-message]').should('be.visible');
  });

  it('seharusnya menampilkan error untuk invalid credentials', () => {
    cy.get('[data-testid=email-input]').type('[email protected]');
    cy.get('[data-testid=password-input]').type('wrongpassword');
    cy.get('[data-testid=login-button]').click();
    
    cy.get('[data-testid=error-message]')
      .should('be.visible')
      .and('contain', 'Invalid credentials');
  });

  it('seharusnya menampilkan validation errors untuk empty fields', () => {
    cy.get('[data-testid=login-button]').click();
    
    cy.get('[data-testid=email-error]').should('contain', 'Email is required');
    cy.get('[data-testid=password-error]').should('contain', 'Password is required');
  });
});

Advanced Cypress Patterns

// cypress/e2e/shopping.cy.js
describe('Shopping Cart Flow', () => {
  beforeEach(() => {
    cy.visit('http://localhost:3000');
    // Login before each test
    cy.login('[email protected]', 'password123');
  });

  it('seharusnya add items to cart dan checkout', () => {
    // Navigate to products
    cy.get('[data-testid=nav-products]').click();
    
    // Add first product
    cy.get('[data-testid=product-card]').first().within(() => {
      cy.get('[data-testid=add-to-cart]').click();
    });
    
    // Verify cart count updated
    cy.get('[data-testid=cart-count]').should('contain', '1');
    
    // Go to cart
    cy.get('[data-testid=nav-cart]').click();
    
    // Checkout
    cy.get('[data-testid=checkout-button]').click();
    
    // Fill shipping info
    cy.get('[data-testid=address-input]').type('123 Main St');
    cy.get('[data-testid=city-input]').type('New York');
    cy.get('[data-testid=zip-input]').type('10001');
    
    // Complete payment
    cy.get('[data-testid=pay-button]').click();
    
    // Verify success
    cy.url().should('include', '/order-confirmation');
    cy.get('[data-testid=success-message]').should('be.visible');
  });

  it('seharusnya menangani kesalahan jaringan dengan baik', () => {
    // Intercept and fail the API call
    cy.intercept('GET', '/api/products', { 
      statusCode: 500,
      body: { error: 'Server error' }
    });
    
    cy.get('[data-testid=nav-products]').click();
    cy.get('[data-testid=error-message]').should('contain', 'Failed to load products');
  });
});

Custom Cypress Commands

Buat perintah yang dapat digunakan kembali untuk operasi test umum:

// cypress/support/commands.js
Cypress.Commands.add('login', (email, password) => {
  cy.visit('http://localhost:3000/login');
  cy.get('[data-testid=email-input]').type(email);
  cy.get('[data-testid=password-input]').type(password);
  cy.get('[data-testid=login-button]').click();
  cy.url().should('include', '/dashboard');
});

Cypress.Commands.add('addToCart', (productId) => {
  cy.get(`[data-testid=product-${productId}]`).within(() => {
    cy.get('[data-testid=add-to-cart]').click();
  });
});

E2E Testing dengan Playwright

Playwright adalah kerangka kerja pengujian E2E modern yang mendukung berbagai peramban (Chromium, Firefox, WebKit) dengan kemampuan pengujian lintas peramban yang sangat baik.

Playwright Setup

npm install --save-dev @playwright/test
npx playwright install
// tests/auth.spec.js
import { test, expect } from '@playwright/test';

test.describe('Authentication', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('http://localhost:3000/login');
  });

  test('seharusnya login successfully', async ({ page }) => {
    await page.fill('[data-testid=email]', '[email protected]');
    await page.fill('[data-testid=password]', 'password123');
    await page.click('[data-testid=login-button]');
    
    await expect(page).toHaveURL(/.*dashboard/);
    await expect(page.locator('[data-testid=welcome]')).toBeVisible();
  });

  test('seharusnya show error untuk invalid credentials', async ({ page }) => {
    await page.fill('[data-testid=email]', '[email protected]');
    await page.fill('[data-testid=password]', 'wrong');
    await page.click('[data-testid=login-button]');
    
    const error = page.locator('[data-testid=error-message]');
    await expect(error).toBeVisible();
    await expect(error).toContainText('Invalid credentials');
  });
});

Cross-Browser Testing dengan Playwright

// tests/cross-browser.spec.js
import { test, expect } from '@playwright/test';

test.describe('Kompatibilitas lintas peramban', () => {
  // Otomatis berjalan di Chromium, Firefox, dan WebKit
  test('seharusnya render dengan benar', async ({ page }) => {
    await page.goto('http://localhost:3000');
    
    const header = page.locator('header');
    await expect(header).toBeVisible();
    
    // Check visual consistency
    await expect(page).toHaveScreenshot();
  });
});

Test Coverage dan Metrics

Cakupan kode mengukur persentase kode Kamu yang dijalankan oleh test. Meskipun bukan satu-satunya metrik kualitas, ini adalah indikator yang berguna untuk kelengkapan pengujian.

Memahami Coverage Reports

File Coverage Report:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 File              | Statements | Branches | Funcs | Lines
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 All files        |    85.2%   |   78.5%  | 82.1% | 85.2%
 userService.js   |    92.3%   |   85.7%  | 100%  | 92.3%
 utils.js         |    78.9%   |   71.4%  | 75%   | 78.9%
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Mengatur Coverage Thresholds

Konfigurasikan persyaratan cakupan minimum di Jest:

// jest.config.js
module.exports = {
  collectCoverageFrom: [
    'src/**/*.{js,jsx}',
    '!src/index.js',
    '!src/**/*.stories.js'
  ],
  coverageThreshold: {
    global: {
      branches: 75,
      functions: 75,
      lines: 75,
      statements: 75
    },
    './src/utils/': {
      branches: 90,
      functions: 90,
      lines: 90,
      statements: 90
    }
  }
};

Interpreting Coverage Metrics

Statements: Persentase individual code statements yang dieksekusi. Penting tetapi tidak cukup sendiri.

Branches: Persentase conditional branches (if/else) yang dieksekusi. Critical untuk memahami edge case.

Functions: Persentase functions yang dipanggil. High function coverage tidak menjamin thorough testing.

Lines: Persentase code line yang dieksekusi. Mirip dengan statements tetapi count berbeda.

Targetkan meaningful coverage (70-80%) daripada mengejar 100%, yang dapat menyebabkan testing implementation details.

Testing Best Practices

Membangun sustainable testing practice memerlukan mengikuti proven patterns dan principles. Best practices ini membantu membuat test yang maintainable, reliable, dan valuable.

Test Naming dan Organization

Tulis clear test descriptions yang menjelaskan behavior apa yang sedang ditest:

describe('calculateDiscount', () => {
  // ✅ Clear: describes what happens under specific conditions
  it('seharusnya apply 10% discount ketika customer punya loyalty status', () => {
    // test code
  });

  // ✅ Clear: explains the edge case being tested
  it('seharusnya tidak apply discount jika total dibawah minimum threshold', () => {
    // test code
  });

  // ❌ Vague: doesn't describe expected behavior
  it('works correctly', () => {
    // test code
  });
});

Test Isolation dan Independence

Setiap test harus independent dan tidak bergantung pada state dari test lain. Gunakan setup dan teardown function appropriately:

describe('Database operations', () => {
  let db;

  beforeEach(async () => {
    // Fresh database state before each test
    db = await setupTestDatabase();
  });

  afterEach(async () => {
    // Clean up after each test
    await db.clear();
  });

  it('should create a record', async () => {
    // Test runs with clean database
    const result = await db.create({ name: 'Test' });
    expect(result.id).toBeDefined();
  });

  it('should find a record', async () => {
    // This test starts fresh, doesn't depend on previous test
    const record = await db.create({ name: 'Test' });
    const found = await db.findById(record.id);
    expect(found.name).toBe('Test');
  });
});

Hindari Testing Implementation Details

Fokus pada testing behavior dan outcomes, bukan internal implementation. Ini membuat test lebih robust untuk refactoring:

// Component to test
function Counter() {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <p data-testid="count">Count: {count}</p>
      <button onClick={() => setCount(c => c + 1)}>Increment</button>
    </div>
  );
}

// ❌ Bad: Tests implementation detail (useState)
it('should initialize count state to 0', () => {
  const { result } = renderHook(() => useState(0));
  expect(result.current[0]).toBe(0);
});

// ✅ Good: Tests behavior (what user sees)
it('should display initial count of 0', () => {
  render(<Counter />);
  expect(screen.getByTestId('count')).toHaveTextContent('Count: 0');
});

it('should increment count when button is clicked', () => {
  render(<Counter />);
  fireEvent.click(screen.getByText('Increment'));
  expect(screen.getByTestId('count')).toHaveTextContent('Count: 1');
});

DRY Principle dalam Tests

Extract common test setup ke helper functions dan factories:

// Helper function to reduce duplication
function renderWithProviders(component) {
  return render(
    <QueryClientProvider client={queryClient}>
      <ThemeProvider theme={theme}>
        {component}
      </ThemeProvider>
    </QueryClientProvider>
  );
}

// Factory function for creating test data
function createMockUser(overrides = {}) {
  return {
    id: 1,
    name: 'John Doe',
    email: '[email protected]',
    isActive: true,
    ...overrides
  };
}

describe('UserProfile', () => {
  it('should display user information', () => {
    const user = createMockUser({ name: 'Jane Doe' });
    renderWithProviders(<UserProfile user={user} />);
    
    expect(screen.getByText('Jane Doe')).toBeInTheDocument();
  });
});

Continuous Integration

Integrasikan testing ke CI/CD pipeline Kamu untuk memastikan test berjalan otomatis pada setiap commit dan pull request.

GitHub Actions Configuration

# .github/workflows/test.yml
name: Test

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [16.x, 18.x, 20.x]

    steps:
      - uses: actions/checkout@v2
      
      - uses: actions/setup-node@v2
        with:
          node-version: ${{ matrix.node-version }}
      
      - run: npm ci
      
      - run: npm run test:unit
      
      - run: npm run test:integration
      
      - name: Upload coverage
        uses: codecov/codecov-action@v2
        with:
          files: ./coverage/lcov.info

Pre-commit Testing dengan Husky

npm install --save-dev husky lint-staged
npx husky install
// .husky/pre-commit
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npx lint-staged
// package.json
{
  "lint-staged": {
    "src/**/*.{js,jsx}": "npm run test:related"
  }
}

Debugging Failed Tests

Ketika test fail, systematic debugging membantu mengidentifikasi root causes dengan cepat.

Common Test Failures dan Solutions

// Timeout errors - increase timeout or fix async issues
it('should fetch data', async () => {
  const data = await fetchData();
  expect(data).toBeDefined();
}, 10000); // Increase timeout

// Element not found - verify selectors and timing
it('should display element', async () => {
  render(<AsyncComponent />);
  // Wait for element to appear
  const element = await screen.findByTestId('async-element');
  expect(element).toBeInTheDocument();
});

// State not updated - use waitFor for async state
it('should update state', async () => {
  render(<Component />);
  fireEvent.click(screen.getByRole('button'));
  
  // Wait for state to update
  await waitFor(() => {
    expect(screen.getByText('Updated')).toBeInTheDocument();
  });
});

Debug Mode

Gunakan debug utilities untuk inspect component output:

it('should render correctly', () => {
  const { debug } = render(<Component />);
  
  // Print the DOM tree
  debug();
  
  // Print specific element
  debug(screen.getByTestId('my-element'));
});

Kesimpulan: Membangun Testing Culture

Comprehensive testing bukanlah burden—ini adalah investment yang membayar dividen melalui reduced bug, faster development, dan greater confidence. Mulai dengan unit test untuk critical logic, tambahkan integration test untuk feature workflows, dan gunakan E2E test untuk critical user path.

Ingat: Good test adalah tentang confidence, bukan hanya coverage metrics. Tulis test yang penting, maintain mereka seperti Kamu maintain code, dan saksikan development velocity dan code quality Kamu meningkat.

Last Updated: January 8, 2026