testing

Testing patterns, strategies, and best practices for comprehensive test coverage.

$ Installer

git clone https://github.com/az9713/claude-code-agentic-framework /tmp/claude-code-agentic-framework && cp -r /tmp/claude-code-agentic-framework/skills/testing ~/.claude/skills/claude-code-agentic-framework

// tip: Run this command in your terminal to install the skill


name: testing description: Testing patterns, strategies, and best practices for comprehensive test coverage. triggers:

  • test
  • tests
  • testing
  • coverage
  • unit test
  • integration test

Testing Skill

Overview

This skill defines testing patterns, strategies, and best practices for achieving comprehensive test coverage across the project.

Test Types

TypePurposeLocationSpeed
UnitTest isolated functionstests/unit/Fast
IntegrationTest component interactionstests/integration/Medium
E2ETest full user flowstests/e2e/Slow

Directory Structure

tests/
├── unit/
│   ├── utils/
│   │   └── helpers.test.js
│   └── services/
│       └── userService.test.js
├── integration/
│   ├── api/
│   │   └── userRoutes.test.js
│   └── database/
│       └── userRepository.test.js
├── e2e/
│   └── userFlow.test.js
├── fixtures/
│   └── testData.js
├── helpers/
│   └── testUtils.js
└── run.js

Test File Naming

// Unit tests
[module].test.js
[module].spec.js

// Integration tests
[feature].integration.test.js

// E2E tests
[flow].e2e.test.js

Writing Tests

Basic Test Structure

const { functionToTest } = require('../../app/module');

describe('ModuleName', () => {
  describe('functionToTest', () => {
    // Setup
    beforeEach(() => {
      // Reset state before each test
    });

    afterEach(() => {
      // Cleanup after each test
    });

    // Happy path tests
    describe('when given valid input', () => {
      test('returns expected result', () => {
        const result = functionToTest('valid');
        expect(result).toBe('expected');
      });
    });

    // Edge cases
    describe('edge cases', () => {
      test('handles empty input', () => {
        expect(functionToTest('')).toBe('default');
      });

      test('handles null input', () => {
        expect(functionToTest(null)).toBeNull();
      });
    });

    // Error cases
    describe('error handling', () => {
      test('throws on invalid input', () => {
        expect(() => functionToTest(undefined)).toThrow();
      });
    });
  });
});

AAA Pattern

test('calculateTotal returns correct sum with discount', () => {
  // Arrange - Setup test data
  const items = [{ price: 100 }, { price: 50 }];
  const discount = 0.1;

  // Act - Execute the code
  const result = calculateTotal(items, discount);

  // Assert - Verify the result
  expect(result).toBe(135); // (100 + 50) * 0.9
});

Common Assertions

// Equality
expect(value).toBe(expected);           // Strict equality
expect(value).toEqual(expected);        // Deep equality
expect(value).not.toBe(unexpected);     // Negation

// Truthiness
expect(value).toBeTruthy();
expect(value).toBeFalsy();
expect(value).toBeNull();
expect(value).toBeUndefined();
expect(value).toBeDefined();

// Numbers
expect(value).toBeGreaterThan(3);
expect(value).toBeLessThan(5);
expect(value).toBeCloseTo(0.3, 5);      // For floating point

// Strings
expect(string).toMatch(/pattern/);
expect(string).toContain('substring');

// Arrays
expect(array).toContain(item);
expect(array).toHaveLength(3);

// Objects
expect(object).toHaveProperty('key');
expect(object).toHaveProperty('key', 'value');
expect(object).toMatchObject({ partial: 'match' });

// Errors
expect(() => fn()).toThrow();
expect(() => fn()).toThrow(Error);
expect(() => fn()).toThrow('message');

// Async
await expect(asyncFn()).resolves.toBe(expected);
await expect(asyncFn()).rejects.toThrow();

Testing Async Code

Promises

test('async function resolves correctly', async () => {
  const result = await asyncFunction();
  expect(result).toBe('expected');
});

test('async function rejects on error', async () => {
  await expect(asyncFunction('bad')).rejects.toThrow('Error');
});

Callbacks

test('callback is called with result', (done) => {
  callbackFunction((result) => {
    expect(result).toBe('expected');
    done();
  });
});

Mocking

Mock Functions

const mockFn = jest.fn();

// Set return value
mockFn.mockReturnValue('value');
mockFn.mockResolvedValue('async value');
mockFn.mockRejectedValue(new Error('error'));

// Verify calls
expect(mockFn).toHaveBeenCalled();
expect(mockFn).toHaveBeenCalledWith('arg1', 'arg2');
expect(mockFn).toHaveBeenCalledTimes(2);

Mock Modules

// Mock entire module
jest.mock('../path/to/module');

// Mock with implementation
jest.mock('../path/to/module', () => ({
  functionA: jest.fn().mockReturnValue('mocked'),
  functionB: jest.fn()
}));

// Clear mocks between tests
beforeEach(() => {
  jest.clearAllMocks();
});

Mock Time

beforeEach(() => {
  jest.useFakeTimers();
});

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

test('timeout behavior', () => {
  const callback = jest.fn();
  setTimeout(callback, 1000);

  jest.advanceTimersByTime(1000);
  expect(callback).toHaveBeenCalled();
});

Test Fixtures

// tests/fixtures/testData.js
module.exports = {
  validUser: {
    id: 1,
    name: 'Test User',
    email: 'test@example.com'
  },
  invalidUser: {
    id: null,
    name: '',
    email: 'invalid-email'
  }
};

// Usage in tests
const { validUser, invalidUser } = require('../fixtures/testData');

test('validates user correctly', () => {
  expect(validateUser(validUser)).toBe(true);
  expect(validateUser(invalidUser)).toBe(false);
});

Integration Tests

const request = require('supertest');
const app = require('../../app');
const db = require('../../app/database');

describe('User API', () => {
  beforeAll(async () => {
    await db.connect();
  });

  afterAll(async () => {
    await db.close();
  });

  beforeEach(async () => {
    await db.clear('users');
  });

  test('POST /users creates new user', async () => {
    const response = await request(app)
      .post('/users')
      .send({ name: 'Test', email: 'test@example.com' })
      .expect(201);

    expect(response.body).toHaveProperty('id');
    expect(response.body.name).toBe('Test');
  });

  test('GET /users/:id returns user', async () => {
    const user = await db.insert('users', { name: 'Test' });

    const response = await request(app)
      .get(`/users/${user.id}`)
      .expect(200);

    expect(response.body.name).toBe('Test');
  });
});

Coverage Goals

CategoryTarget
Critical paths100%
Business logic90%+
Error handling80%+
Edge cases70%+
Utilities50%+

Running Tests

# Run all tests
npm test

# Run specific file
npm test -- tests/unit/utils.test.js

# Run with coverage
npm test -- --coverage

# Watch mode
npm test -- --watch

# Run only changed tests
npm test -- --onlyChanged

Test Quality Checklist

  • Tests are independent (no shared state)
  • Tests are deterministic (same result every run)
  • Tests are fast (< 100ms for unit tests)
  • Tests are readable (clear what's being tested)
  • Tests cover happy path
  • Tests cover edge cases
  • Tests cover error cases
  • Mocks are appropriate (not over-mocking)
  • No console.log in tests
  • No skipped tests without explanation