Claude-skill-registry jest-patterns

Jest testing patterns, anti-patterns, and quality rules

install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/jest-patterns" ~/.claude/skills/majiayu000-claude-skill-registry-jest-patterns && rm -rf "$T"
manifest: skills/data/jest-patterns/SKILL.md
source content

Jest Patterns

Modern Jest testing patterns, anti-patterns, and quality rules for JavaScript/TypeScript.

Core Principles

PrincipleDescription
Test BehaviorTest what code does, not how it does it
IsolationEach test independent, no shared state
AAA PatternArrange → Act → Assert structure
Fast FeedbackTests run in milliseconds

Patterns vs Anti-Patterns

Test Structure (AAA)

// GOOD: Clear AAA structure
describe('UserService', () => {
  it('should create user with valid data', async () => {
    // Arrange
    const userData = { name: 'John', email: 'john@example.com' };
    const mockRepo = { save: jest.fn().mockResolvedValue({ id: '1', ...userData }) };
    const service = new UserService(mockRepo);

    // Act
    const result = await service.createUser(userData);

    // Assert
    expect(result.id).toBe('1');
    expect(mockRepo.save).toHaveBeenCalledWith(userData);
  });
});

// BAD: Mixed arrange/act/assert
it('creates user', async () => {
  const service = new UserService({ save: jest.fn() });
  expect(await service.createUser({ name: 'John' })).toBeDefined();
  // What are we testing? Unclear!
});

Mocking

// GOOD: Minimal, focused mocks
const mockFetch = jest.fn().mockResolvedValue({
  ok: true,
  json: () => Promise.resolve({ data: 'test' })
});

// GOOD: Reset mocks between tests
beforeEach(() => {
  jest.clearAllMocks();
});

// GOOD: Mock only what's necessary
jest.mock('./database', () => ({
  query: jest.fn()
}));

// BAD: Over-mocking implementation details
jest.mock('./service', () => ({
  __esModule: true,
  default: jest.fn().mockImplementation(() => ({
    method1: jest.fn(),
    method2: jest.fn(),
    method3: jest.fn(),
    // 20 more methods...
  }))
}));

Assertions

// GOOD: Specific, meaningful assertions
expect(user.name).toBe('John');
expect(errors).toHaveLength(2);
expect(result).toEqual({ id: '1', status: 'active' });
expect(callback).toHaveBeenCalledWith('arg1', expect.any(Number));

// GOOD: Custom matchers for domain concepts
expect(response).toBeSuccessful();
expect(user).toHavePermission('admin');

// BAD: Vague assertions
expect(result).toBeTruthy();
expect(data).toBeDefined();
expect(obj).toMatchObject({}); // Always passes!

// BAD: Multiple unrelated assertions
expect(user.name).toBe('John');
expect(user.email).toBe('john@example.com');
expect(user.createdAt).toBeDefined();
expect(user.updatedAt).toBeDefined();
expect(user.deletedAt).toBeNull();
// Better: Use snapshot or single toEqual

Async Testing

// GOOD: async/await
it('should fetch user data', async () => {
  const data = await fetchUser('123');
  expect(data.name).toBe('John');
});

// GOOD: Test rejections
it('should reject for invalid user', async () => {
  await expect(fetchUser('invalid')).rejects.toThrow('User not found');
});

// GOOD: waitFor for async UI updates
await waitFor(() => {
  expect(screen.getByText('Loaded')).toBeInTheDocument();
});

// BAD: done callback (error-prone)
it('fetches data', (done) => {
  fetchUser('123').then((data) => {
    expect(data).toBeDefined();
    done();
  });
});

// BAD: No await (test passes incorrectly)
it('should fail', () => {
  expect(asyncOperation()).rejects.toThrow(); // Missing await!
});

Test Organization

// GOOD: Descriptive nested describes
describe('ShoppingCart', () => {
  describe('addItem', () => {
    it('should add new item to empty cart', () => {});
    it('should increment quantity for existing item', () => {});
    it('should throw for invalid item', () => {});
  });

  describe('removeItem', () => {
    it('should remove item from cart', () => {});
    it('should throw for item not in cart', () => {});
  });
});

// GOOD: Setup/teardown at appropriate levels
describe('Database tests', () => {
  beforeAll(async () => {
    await db.connect();
  });

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

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

// BAD: Flat, unclear structure
test('cart add', () => {});
test('cart add existing', () => {});
test('cart remove', () => {});
test('cart clear', () => {});

Anti-Patterns to Avoid

Flaky Tests

// BAD: Time-dependent
it('should expire token', () => {
  const token = createToken();
  // Depends on system time - will fail randomly
  expect(isExpired(token)).toBe(false);
});

// GOOD: Control time
it('should expire token after 1 hour', () => {
  jest.useFakeTimers();
  const token = createToken();

  jest.advanceTimersByTime(59 * 60 * 1000);
  expect(isExpired(token)).toBe(false);

  jest.advanceTimersByTime(2 * 60 * 1000);
  expect(isExpired(token)).toBe(true);
});

// BAD: Random data without seed
it('validates random email', () => {
  const email = `user${Math.random()}@test.com`;
  expect(isValid(email)).toBe(true);
});

// GOOD: Deterministic test data
it('validates various email formats', () => {
  const emails = ['user@test.com', 'user.name@test.co.uk'];
  emails.forEach(email => expect(isValid(email)).toBe(true));
});

Test Coupling

// BAD: Tests depend on each other
describe('User flow', () => {
  let userId: string;

  it('creates user', async () => {
    const user = await createUser();
    userId = user.id; // Shared state!
  });

  it('updates user', async () => {
    await updateUser(userId, {}); // Fails if first test fails!
  });
});

// GOOD: Independent tests
describe('User operations', () => {
  it('creates user', async () => {
    const user = await createUser();
    expect(user.id).toBeDefined();
  });

  it('updates existing user', async () => {
    const user = await createUser(); // Own setup
    const updated = await updateUser(user.id, { name: 'New' });
    expect(updated.name).toBe('New');
  });
});

Testing Implementation Details

// BAD: Testing private methods/state
it('should update internal counter', () => {
  const component = new Counter();
  component.increment();
  expect(component._internalCount).toBe(1); // Private!
});

// GOOD: Test public behavior
it('should display incremented value', () => {
  const component = new Counter();
  component.increment();
  expect(component.getValue()).toBe(1);
});

// BAD: Testing method calls instead of effects
it('should call logger', () => {
  const logger = { log: jest.fn() };
  doSomething(logger);
  expect(logger.log).toHaveBeenCalled(); // Is this the real requirement?
});

// GOOD: Test the actual effect
it('should record action in audit log', async () => {
  await doSomething();
  const logs = await getAuditLogs();
  expect(logs).toContainEqual(expect.objectContaining({
    action: 'something',
    timestamp: expect.any(Date)
  }));
});

Snapshot Abuse

// BAD: Large, unstable snapshots
it('renders page', () => {
  expect(render(<FullPage />)).toMatchSnapshot();
  // 500+ line snapshot that changes constantly
});

// GOOD: Small, focused snapshots
it('renders error state correctly', () => {
  expect(render(<ErrorMessage error="Not found" />)).toMatchSnapshot();
});

// GOOD: Inline snapshots for small values
it('formats date correctly', () => {
  expect(formatDate(date)).toMatchInlineSnapshot(`"Jan 13, 2026"`);
});

Configuration Best Practices

// jest.config.js
module.exports = {
  // Clear mocks automatically
  clearMocks: true,

  // Coverage settings
  collectCoverageFrom: ['src/**/*.{ts,tsx}', '!src/**/*.d.ts'],
  coverageThreshold: {
    global: { branches: 80, functions: 80, lines: 80 }
  },

  // Fast test execution
  maxWorkers: '50%',

  // Fail fast in CI
  bail: process.env.CI ? 1 : 0,

  // Clear timeout
  testTimeout: 10000,
};

Quality Checklist

CheckRule
AAA structureArrange → Act → Assert clearly separated
IsolationTests don't share state
No flaky testsNo time/random dependencies
Fast executionUnit tests < 100ms each
Clear assertionsSpecific matchers, not toBeTruthy
Minimal mockingOnly mock what's necessary
Descriptive namesTest name describes behavior
CleanupafterEach/afterAll for side effects