Testing Skill
Instructions
When writing tests:
- 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);
- 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();
}); });
- 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],
];
}
}
- 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);
}
}
- 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');
}); });
- 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
- 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
- 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();