Claude Code Plugins

Community-maintained marketplace

Feedback

Write tests for JavaScript, React, PHP, and WordPress including unit tests, integration tests, and E2E tests. Use when writing tests, setting up testing frameworks, or debugging test failures.

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
description Write tests for JavaScript, React, PHP, and WordPress including unit tests, integration tests, and E2E tests. Use when writing tests, setting up testing frameworks, or debugging test failures.

Testing Skill

Instructions

When writing tests:

1. JavaScript Testing (Jest)

Setup:

// jest.config.js
module.exports = {
  testEnvironment: 'jsdom',
  setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
  moduleNameMapper: {
    '^@/(.*)$': '<rootDir>/src/$1',
  },
  collectCoverageFrom: ['src/**/*.{js,jsx}'],
};

Unit Test Example:

// utils.test.js
import { formatPrice, validateEmail } from './utils';

describe('formatPrice', () => {
  it('formats number as currency', () => {
    expect(formatPrice(1000)).toBe('$1,000.00');
  });

  it('handles zero', () => {
    expect(formatPrice(0)).toBe('$0.00');
  });

  it('handles negative numbers', () => {
    expect(formatPrice(-50)).toBe('-$50.00');
  });
});

describe('validateEmail', () => {
  it('returns true for valid email', () => {
    expect(validateEmail('user@example.com')).toBe(true);
  });

  it('returns false for invalid email', () => {
    expect(validateEmail('invalid')).toBe(false);
    expect(validateEmail('no@domain')).toBe(false);
  });
});

Async Test:

describe('fetchUser', () => {
  it('fetches user data', async () => {
    const user = await fetchUser(1);
    expect(user).toHaveProperty('name');
    expect(user.id).toBe(1);
  });

  it('throws on invalid id', async () => {
    await expect(fetchUser(-1)).rejects.toThrow('Invalid ID');
  });
});

Mocking:

// Mock module
jest.mock('./api');
import { fetchData } from './api';

fetchData.mockResolvedValue({ data: 'mocked' });

// Mock function
const mockCallback = jest.fn();
mockCallback.mockReturnValue(42);

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

2. React Testing (React Testing Library)

Component Test:

import { render, screen, fireEvent } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import Button from './Button';

describe('Button', () => {
  it('renders with label', () => {
    render(<Button>Click me</Button>);
    expect(screen.getByRole('button', { name: /click me/i })).toBeInTheDocument();
  });

  it('calls onClick when clicked', async () => {
    const handleClick = jest.fn();
    render(<Button onClick={handleClick}>Click</Button>);

    await userEvent.click(screen.getByRole('button'));
    expect(handleClick).toHaveBeenCalledTimes(1);
  });

  it('is disabled when loading', () => {
    render(<Button loading>Submit</Button>);
    expect(screen.getByRole('button')).toBeDisabled();
  });
});

Form Test:

import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import LoginForm from './LoginForm';

describe('LoginForm', () => {
  it('submits form with valid data', async () => {
    const onSubmit = jest.fn();
    render(<LoginForm onSubmit={onSubmit} />);

    await userEvent.type(screen.getByLabelText(/email/i), 'user@example.com');
    await userEvent.type(screen.getByLabelText(/password/i), 'password123');
    await userEvent.click(screen.getByRole('button', { name: /log in/i }));

    await waitFor(() => {
      expect(onSubmit).toHaveBeenCalledWith({
        email: 'user@example.com',
        password: 'password123',
      });
    });
  });

  it('shows error for invalid email', async () => {
    render(<LoginForm onSubmit={jest.fn()} />);

    await userEvent.type(screen.getByLabelText(/email/i), 'invalid');
    await userEvent.click(screen.getByRole('button', { name: /log in/i }));

    expect(await screen.findByText(/invalid email/i)).toBeInTheDocument();
  });
});

3. PHP Testing (PHPUnit)

Setup:

<!-- phpunit.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="tests/bootstrap.php" colors="true">
    <testsuites>
        <testsuite name="Unit">
            <directory>tests/Unit</directory>
        </testsuite>
    </testsuites>
</phpunit>

Unit Test:

<?php
// tests/Unit/HelperTest.php
use PHPUnit\Framework\TestCase;

class HelperTest extends TestCase
{
    public function test_format_price_returns_formatted_string()
    {
        $result = format_price(1000);
        $this->assertEquals('$1,000.00', $result);
    }

    public function test_sanitize_title_removes_special_chars()
    {
        $result = sanitize_title('Hello World!');
        $this->assertEquals('hello-world', $result);
    }

    /**
     * @dataProvider emailProvider
     */
    public function test_validate_email($email, $expected)
    {
        $this->assertEquals($expected, validate_email($email));
    }

    public function emailProvider(): array
    {
        return [
            ['user@example.com', true],
            ['invalid', false],
            ['', false],
        ];
    }
}

4. WordPress Testing

Setup:

<?php
// tests/bootstrap.php
$_tests_dir = getenv('WP_TESTS_DIR') ?: '/tmp/wordpress-tests-lib';
require_once $_tests_dir . '/includes/functions.php';

function _manually_load_plugin() {
    require dirname(__DIR__) . '/plugin-name.php';
}
tests_add_filter('muplugins_loaded', '_manually_load_plugin');

require $_tests_dir . '/includes/bootstrap.php';

Plugin Test:

<?php
class PluginTest extends WP_UnitTestCase
{
    public function test_plugin_is_activated()
    {
        $this->assertTrue(is_plugin_active('plugin-name/plugin-name.php'));
    }

    public function test_custom_post_type_is_registered()
    {
        $this->assertTrue(post_type_exists('portfolio'));
    }

    public function test_shortcode_renders_content()
    {
        $output = do_shortcode('[my_shortcode]');
        $this->assertStringContainsString('expected-content', $output);
    }
}

5. E2E Testing (Playwright)

// tests/e2e/login.spec.js
import { test, expect } from '@playwright/test';

test.describe('Login Flow', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('/login');
  });

  test('successful login redirects to dashboard', async ({ page }) => {
    await page.fill('[name="email"]', 'user@example.com');
    await page.fill('[name="password"]', 'password123');
    await page.click('button[type="submit"]');

    await expect(page).toHaveURL('/dashboard');
    await expect(page.locator('h1')).toContainText('Welcome');
  });

  test('invalid credentials show error', async ({ page }) => {
    await page.fill('[name="email"]', 'wrong@example.com');
    await page.fill('[name="password"]', 'wrongpassword');
    await page.click('button[type="submit"]');

    await expect(page.locator('.error')).toBeVisible();
    await expect(page.locator('.error')).toContainText('Invalid credentials');
  });
});

6. Test Structure

tests/
├── unit/           # Pure function tests
├── integration/    # Component interaction tests
├── e2e/           # Full user flow tests
├── fixtures/      # Test data
├── mocks/         # Mock implementations
└── helpers/       # Test utilities

7. Testing Best Practices

  • Test behavior, not implementation
  • One assertion per test (when practical)
  • Use descriptive test names
  • Arrange-Act-Assert pattern
  • Don't test external libraries
  • Keep tests fast and isolated
  • Use factories for test data
  • Mock external dependencies
  • Aim for 80%+ coverage on critical paths
  • Run tests in CI/CD

8. Common Matchers

// Jest/Vitest
expect(value).toBe(exact);
expect(value).toEqual(deepEqual);
expect(value).toBeTruthy();
expect(value).toBeFalsy();
expect(value).toBeNull();
expect(value).toContain(item);
expect(value).toHaveLength(3);
expect(value).toMatch(/regex/);
expect(fn).toThrow(Error);
expect(fn).toHaveBeenCalled();