Strategi Testing untuk Web Apps — Panduan Quality Assurance Komprehensif

Strategi Testing untuk Web Apps — Panduan Quality Assurance Komprehensif

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

Pengenalan: Membangun Kepercayaan Melalui Testing

Testing adalah fondasi pengembangan software profesional. Strategi testing komprehensif memastikan kualitas kode, mencegah regresi, menyediakan dokumentasi, dan menghemat waktu serta biaya dengan menangkap bug sebelum mencapai production. Di 2026, testing bukan lagi opsional—ini adalah kebutuhan 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 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 kode berfungsi sesuai harapan dan menangkap regresi sejak dini dalam siklus pengembangan. Basis kode yang diuji dengan baik secara signifikan lebih dapat diKamulkan dan mudah dipelihara.

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

Pengurangan Biaya: Memperbaiki bug di production jauh lebih mahal daripada menangkapnya saat pengembangan. Test mengurangi biaya maintenance dan support.

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

Dokumentasi: Test berfungsi sebagai dokumentasi executable yang menunjukkan cara menggunakan kode dan perilaku 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 testing pyramid Kamu.

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

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

Unit Testing dengan Jest

Jest adalah testing framework standar industri untuk aplikasi JavaScript dan React. 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 Arrange-Act-Assert: 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 pesan error lebih informatif.

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 side effects atau dependencies 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 robust dan sesuai 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 detail implementasi. Render komponen dan verifikasi apa yang dilihat dan bisa 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

Integration test harus menguji fitur, bukan komponen individual. Fokus pada perilaku yang terlihat 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 when fetch fails', 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 framework yang powerful untuk menguji user workflows lengkap di real browser. Test ditulis dengan JavaScript dan berjalan dalam konteks browser, membuat debugging jadi 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('should add items to cart and 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('should handle network errors gracefully', () => {
    // 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 custom commands yang reusable 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 framework E2E testing modern yang mendukung multiple browsers (Chromium, Firefox, WebKit) dengan kemampuan cross-browser testing yang excellent.

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('should 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('should show error for 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 cases.

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

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

Targetkan meaningful coverage (70-80%) daripada mengejar 100%, yang bisa mengakibatkan 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 untuk jangka panjang.

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 and Independence

Setiap test harus independent dan tidak bergantung pada state dari test lain. Gunakan setup dan teardown functions dengan tepat:

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

  beforeEach(async () => {
    // Sediakan fresh database state sebelum setiap test
    db = await setupTestDatabase();
  });

  afterEach(async () => {
    // Cleanup setelah setiap test
    await db.clear();
  });

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

  it('should find a record', async () => {
    // Test ini mulai fresh, tidak tergantung test sebelumnya
    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 - tingkatkan timeout atau fix async issues
it('should fetch data', async () => {
  const data = await fetchData();
  expect(data).toBeDefined();
}, 10000); // Tingkatkan timeout

// Element not found - verifikasi selectors dan timing
it('should display element', async () => {
  render(<AsyncComponent />);
  // Tunggu element muncul
  const element = await screen.findByTestId('async-element');
  expect(element).toBeInTheDocument();
});

// State not updated - gunakan waitFor untuk async state
it('should update state', async () => {
  render(<Component />);
  fireEvent.click(screen.getByRole('button'));
  
  // Tunggu state 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 DOM tree
  debug();
  
  // Print element spesifik
  debug(screen.getByTestId('my-element'));
});

Kesimpulan: Membangun Testing Culture

Testing komprehensif bukan beban, melainkan investasi yang memberikan hasil nyata: lebih sedikit bug, development lebih cepat, dan kepercayaan yang lebih tinggi. Mulai dengan unit test untuk logika penting, tambahkan integration test untuk alur fitur, dan gunakan E2E test untuk user workflows yang kritis.

Ingat: Tes yang baik adalah tentang kepercayaan diri, bukan hanya metrics coverage. Tulis test yang meaningful, rawat mereka seperti Kamu merawat kode production, dan saksikan kecepatan development dan kualitas kode Kamu meningkat signifikan.

Last Updated: January 24, 2026