Claude Code Plugins

Community-maintained marketplace

Feedback

testing-automation

@pwarnock/liaison-toolkit
2
0

Testing patterns with Bun test runner, coverage thresholds, mocking, and CI/CD integration. Use when writing tests, organizing test files, or setting up quality gates.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name testing-automation
description Testing patterns with Bun test runner, coverage thresholds, mocking, and CI/CD integration. Use when writing tests, organizing test files, or setting up quality gates.
license MIT
metadata [object Object]

Testing Automation

Patterns and best practices for testing TypeScript projects with Bun test runner, including coverage thresholds, mocking strategies, and CI/CD integration.

When to use this skill

Use this skill when:

  • Writing unit tests or integration tests
  • Organizing test file structure
  • Configuring test coverage thresholds
  • Mocking dependencies for CLI testing
  • Setting up CI/CD test pipelines
  • Debugging failing tests

Bun Test Runner Basics

Running Tests

# Run all tests
bun test

# Run tests in watch mode
bun test --watch

# Run tests with coverage
bun test --coverage

# Run specific test file
bun test path/to/specific.test.ts

# Run tests matching pattern
bun test --pattern "**/*command.test.ts"

Test File Organization

// src/cli.test.ts (test file alongside source)
import { describe, it, expect, beforeEach, afterEach } from 'bun:test';

describe('CLI Command', () => {
  beforeEach(() => {
    // Setup before each test
  });

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

  it('should parse command arguments correctly', async () => {
    const result = parseCommand(['create', 'test']);
    expect(result.name).toBe('test');
  });
});

Test Types

// Unit tests - test individual functions
it('should validate skill name format', () => {
  expect(validateSkillName('test-skill')).toBe(true);
  expect(validateSkillName('Invalid_Name')).toBe(false);
});

// Integration tests - test workflows
it('should create skill with all subdirectories', async () => {
  await createSkill('test-skill');
  const skillExists = await fs.exists('.skills/test-skill/SKILL.md');
  expect(skillExists).toBe(true);
});

// Smoke tests - basic functionality tests
it('should run without errors', async () => {
  const result = await executeCommand(['list']);
  expect(result.exitCode).toBe(0);
});

Test Coverage

Coverage Thresholds

From qa-subagent configuration in agents/qa-subagent.json:

// Target: 80% code coverage
const coverageThreshold = 80;

Generating Coverage Reports

# Run tests with coverage
bun test --coverage

# Coverage output in coverage/ directory
# - coverage/index.html - HTML report
# - coverage/coverage-final.json - JSON for CI

Enforcing Coverage in CI

# .github/workflows/test.yml
- name: Check coverage
  run: bun test --coverage
- name: Verify threshold
  run: |
    COVERAGE=$(cat coverage/coverage-summary.json | jq '.total.lines.pct')
    if (( $(echo "$COVERAGE < 80" | bc -l) )); then
      echo "Coverage $(COVERAGE)% is below 80%"
      exit 1
    fi

Mocking and Stubbing

Mocking File System

import { mockFS } from 'bun:test/mock';

mockFS({
  '.skills/test-skill/SKILL.md': 'content here',
}, async () => {
  await createSkill('test-skill');
  const content = await fs.readFile('.skills/test-skill/SKILL.md', 'utf-8');
  expect(content).toBe('content here');
});

Mocking CLI Input

import { mockProcess } from 'bun:test/mock';

mockProcess({
  argv: ['create', 'test-skill'],
  stdin: 'y\n',  // Simulate user input
});

await executeCommand();
// Verify command behavior with mocked input

Mocking External Services

import { mock, spyOn } from 'bun:test';

// Mock Context7 API
const mockContext7 = mock(() => ({
  resolveLibrary: async (name: string) => ({ id: name, docs: '...' }),
}));

// Use mock in test
const result = await mockContext7.resolveLibrary('react');
expect(result.docs).toBeDefined();

CLI Testing Patterns

Capturing Output

import { stdout, stderr } from 'bun:test';

const spyStdout = spyOn(stdout, 'write');
const spyStderr = spyOn(stderr, 'write');

await executeCommand(['list']);

expect(spyStdout).toHaveBeenCalled();
expect(spyStderr).not.toHaveBeenCalled();

Testing Exit Codes

import { mockProcess } from 'bun:test/mock';

mockProcess({
  exit: (code: number) => {
    exitCode = code;
  },
});

await executeCommand(['invalid-command']);
expect(exitCode).toBe(1);

Testing Error Messages

it('should show error message on invalid input', async () => {
  const spyConsoleError = spyOn(console, 'error');

  await executeCommand(['create', 'Invalid_Name']);

  expect(spyConsoleError).toHaveBeenCalledWith(
    expect.stringContaining('Invalid skill name')
  );
});

CI/CD Integration

GitHub Actions Test Workflow

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

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: oven-sh/setup-bun@v2
      - name: Install dependencies
        run: bun install
      - name: Run tests
        run: bun test --coverage
      - name: Upload coverage
        uses: codecov/codecov-action@v4
        with:
          files: ./coverage/coverage-final.json

Test Matrix Strategy

# Test across multiple Node versions and OS
strategy:
  matrix:
    os: [ubuntu-latest, macos-latest, windows-latest]
    bun-version: ['1.0.x', 'latest']

steps:
  - name: Run tests
    run: bun test

Quality Gates

Pre-commit Test Hooks

// package.json
{
  "scripts": {
    "test": "bun test",
    "test:watch": "bun test --watch",
    "test:coverage": "bun test --coverage",
    "precommit": "bun test"
  }
}

Linting Tests

# Run linter on test files
bun lint src/**/*.test.ts

# Format test files
bun format src/**/*.test.ts

Verification

After implementing test infrastructure:

  • Test files organized alongside source code
  • Coverage threshold (80%) is enforced
  • Mocks are used for external dependencies
  • CI/CD runs tests on every PR
  • Output capture tests verify correct formatting
  • Exit code tests validate error handling
  • Pre-commit hooks prevent broken tests from committing

Examples from liaison-toolkit

Example 1: Testing Skill List Command

// packages/liaison/__tests__/skill.test.ts
import { describe, it, expect, mock } from 'bun:test';

describe('skill list command', () => {
  it('should list all available skills', async () => {
    // Mock discoverSkills
    mock(() => ({
      '.skills/library-research/SKILL.md': '...',
      '.skills/git-automation/SKILL.md': '...',
    }), async () => {
      const result = await executeCommand(['skill', 'list']);
      expect(result.skills.length).toBeGreaterThan(0);
    });
  });
});

Example 2: Testing Validation

import { describe, it, expect } from 'bun:test';

describe('skill validation', () => {
  it('should reject invalid skill names', async () => {
    const result = await validateSkillName('Invalid_Name');
    expect(result.valid).toBe(false);
    expect(result.errors[0].type).toBe('invalid-name');
  });

  it('should accept valid skill names', async () => {
    const result = await validateSkillName('test-skill');
    expect(result.valid).toBe(true);
  });
});

Related Resources