Testing Approaches in JavaScript

Testing is crucial for maintaining reliable JavaScript applications. Understanding different testing approaches and tools helps ensure code quality and prevent regressions.

Unit Testing

// Jest Example
describe('Calculator', () => {
  let calculator;
  
  beforeEach(() => {
    calculator = new Calculator();
  });
  
  test('adds two numbers correctly', () => {
    expect(calculator.add(2, 3)).toBe(5);
  });
  
  test('throws error for invalid input', () => {
    expect(() => calculator.add('2', 3))
      .toThrow('Invalid input');
  });
});

// Testing async functions
test('fetches user data', async () => {
  const data = await fetchUserData(1);
  expect(data).toMatchObject({
    id: 1,
    name: expect.any(String)
  });
});

Integration Testing

// Testing API integration
describe('UserService', () => {
  it('creates and retrieves user', async () => {
    // Create user
    const user = await UserService.create({
      name: 'John',
      email: 'john@example.com'
    });
    
    // Retrieve user
    const retrieved = await UserService.get(user.id);
    expect(retrieved).toEqual(user);
  });
});

// Testing component integration
describe('ShoppingCart', () => {
  it('updates total when adding items', async () => {
    const cart = new ShoppingCart();
    const product = await ProductService.get(1);
    
    await cart.addItem(product);
    expect(cart.total).toBe(product.price);
  });
});

E2E Testing

// Cypress Example
describe('Login Flow', () => {
  it('successfully logs in', () => {
    cy.visit('/login');
    cy.get('[data-test=email]')
      .type('user@example.com');
    cy.get('[data-test=password]')
      .type('password123');
    cy.get('[data-test=submit]')
      .click();
    cy.url().should('include', '/dashboard');
  });
});

// Playwright Example
test('user registration', async ({ page }) => {
  await page.goto('/register');
  await page.fill('input[name="email"]', 'test@example.com');
  await page.fill('input[name="password"]', 'password123');
  await page.click('button[type="submit"]');
  await expect(page).toHaveURL('/dashboard');
});

Testing Best Practices

// Arrange-Act-Assert Pattern
test('updates user profile', async () => {
  // Arrange
  const user = await createTestUser();
  const newData = { name: 'New Name' };
  
  // Act
  await updateProfile(user.id, newData);
  
  // Assert
  const updated = await getUser(user.id);
  expect(updated.name).toBe(newData.name);
});

// Test Doubles
const mockUserService = {
  getUser: jest.fn(),
  updateUser: jest.fn()
};

test('handles error gracefully', async () => {
  mockUserService.getUser.mockRejectedValue(new Error());
  
  await expect(getUserProfile(1))
    .rejects.toThrow();
});

Common Interview Follow-up Questions

  1. What's the difference between unit and integration tests?
  2. How do you decide what to test?
  3. What are the benefits of TDD?
  4. How do you handle testing async code?

Best Practices

  • Write tests before fixing bugs
  • Keep tests focused and isolated
  • Use meaningful test descriptions
  • Follow the AAA pattern
  • Maintain test coverage standards