agentsclimarketplace

Test unit

Skill kensaurus/cursor-kenji/skills/test-unit

🦖Curated Cursor AI agent skills, slash commands, MCP configs, subagents & rules for full-stack dev — React 19, Next.js 15, Supabase, Tailwind v4, TypeScript

Install
npx -y skills add kensaurus/cursor-kenji --skill test-unit

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 6 stars6 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Write effective unit tests with best practices for any project. Auto-detects test framework (Jest, Vitest, pytest, Go test, etc.), researches current testing patterns via Firecrawl, fetches testing library docs via Context7, and uses Sentry MCP to identify production errors that lack test coverage. Use when writing tests, creating test cases, improving test coverage, increasing confidence before release, or when the user mentions testing.

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

12.7 KB, as published. Nobody here has run it

Unit Testing Skill

Write effective, maintainable unit tests informed by the project's actual framework, production error data, and current best practices.


Step 0: Auto-Detect Test Environment

Before writing any tests, discover the project's testing setup.

0a. Detect Test Framework

Read the dependency manifest (package.json, requirements.txt, pyproject.toml, go.mod, Cargo.toml, build.gradle, Gemfile) and look for:

FrameworkDetection Signal
Vitestvitest in devDependencies, vitest.config.ts
Jestjest in devDependencies, jest.config.*, "jest" key in package.json
Testing Library@testing-library/react, @testing-library/vue, etc.
Playwright@playwright/test in devDependencies, playwright.config.ts
Cypresscypress in devDependencies, cypress.config.*
pytestpytest in requirements, conftest.py files
Go test_test.go files, go test in Makefile
RSpecrspec in Gemfile, spec/ directory
JUnitjunit in build.gradle, src/test/ directory

0b. Discover Existing Test Patterns

Glob: **/*.test.{ts,tsx,js,jsx} → JS/TS test files
Glob: **/*.spec.{ts,tsx,js,jsx} → JS/TS spec files
Glob: **/test_*.py → Python test files
Glob: **/*_test.go → Go test files
Glob: **/spec/**/*_spec.rb → Ruby spec files

Read 2-3 existing test files to understand:

  • Import patterns and test utilities used
  • Naming conventions (describe/it vs test)
  • Mock/stub patterns (vi.mock, jest.mock, unittest.mock)
  • Setup/teardown patterns (beforeEach, afterEach, fixtures)
  • Assertion library (expect, assert, chai)

0c. Detect Test Configuration

Glob: **/vitest.config.*
Glob: **/jest.config.*
Glob: **/pytest.ini
Glob: **/conftest.py
Glob: **/setup.cfg
Glob: **/.nycrc*
Glob: **/c8.config.*

Extract: coverage thresholds, test directories, global setup files, module aliases.

0d. Check Coverage Status

If there's a coverage tool configured (c8, istanbul/nyc, coverage.py):

# Check if coverage script exists
Grep: "coverage" in package.json scripts section
Grep: "c8" or "nyc" or "istanbul" in package.json

0e. Record Discovery

TEST ENVIRONMENT:
- Framework: [Vitest/Jest/pytest/etc. + version]
- Assertion style: [expect/assert/chai]
- Component testing: [Testing Library/Enzyme/none]
- Mock pattern: [vi.mock/jest.mock/unittest.mock]
- Coverage tool: [c8/nyc/coverage.py/none]
- Coverage threshold: [X% or not configured]
- Test directory: [__tests__/tests/spec/co-located]
- Existing tests: [count]
- Config file: [path]

Step 1: Research Testing Best Practices

Before writing tests, research current best practices for the detected framework.

1a. Context7 — Official Testing Library Docs

Fetch docs for the detected test framework:

context7:resolve-library-id
{
 "libraryName": "<DETECTED_FRAMEWORK>",
 "query": "unit testing best practices"
}

Then fetch specific guidance:

context7:query-docs
{
 "libraryId": "<RESOLVED_ID>",
 "query": "testing patterns mocking setup teardown"
}

Do this for each major testing dependency (e.g., vitest, @testing-library/react, msw).

1b. Firecrawl — Current Testing Patterns

firecrawl:firecrawl_search
{
 "query": "<FRAMEWORK> unit testing best practices [current year]",
 "limit": 5,
 "sources": [{ "type": "web" }]
}

Run 2-3 searches targeting different aspects:

SearchQuery
Best practices<framework> testing best practices [current year]
Mocking patterns<framework> mocking API calls best practices
Component testingReact Testing Library component testing patterns [current year]
Coverage strategyunit test coverage strategy meaningful tests vs coverage percentage

Scrape the 1-2 most authoritative results for implementation details:

firecrawl:firecrawl_scrape
{
 "url": "<AUTHORITATIVE_URL>",
 "formats": ["markdown"],
 "onlyMainContent": true
}

Step 2: Analyze Coverage Gaps (Sentry Integration)

Use Sentry MCP to find production errors that should have been caught by tests.

2a. Find Unresolved Production Errors

sentry:search_issues
{
 "organizationSlug": "<ORG_SLUG>",
 "query": "unresolved errors from the last 30 days",
 "projectSlugOrId": "<PROJECT_SLUG>",
 "regionUrl": "<REGION_URL>",
 "limit": 25
}

2b. Cross-Reference with Test Coverage

For each Sentry error:

  1. Identify the source file from the stack trace
  2. Check if a corresponding test file exists (Glob: **/<filename>.test.*)
  3. If tests exist, check whether they cover the failing code path
  4. If no tests exist, prioritize writing tests for this file

2c. Generate Priority List

COVERAGE GAP ANALYSIS:
- Production errors without tests: [count]
 1. [file] — [error type] — [frequency] — NO TEST FILE
 2. [file] — [error type] — [frequency] — test exists but missing edge case
- Highest-impact files to test: [ordered list]

Step 3: Write Tests

Test Structure (AAA Pattern)

describe('ComponentOrModule', () => {
 describe('methodOrBehavior', () => {
 it('should [expected behavior] when [condition]', () => {
 // Arrange — set up test data and dependencies
 const input = createTestData();

 // Act — execute the behavior under test
 const result = functionUnderTest(input);

 // Assert — verify the outcome
 expect(result).toEqual(expectedOutput);
 });
 });
});

Naming Conventions

Test file names — co-locate with source or mirror directory structure:

Source FileTest File
src/utils/formatDate.tssrc/utils/formatDate.test.ts
src/components/Button.tsxsrc/components/Button.test.tsx
app/services/user.pytests/services/test_user.py

Test descriptions — use should [behavior] when [condition]:

describe('validateEmail', () => {
 it('should return true for valid email', () => { /* ... */ });
 it('should return false when @ is missing', () => { /* ... */ });
 it('should return false for empty string', () => { /* ... */ });
 it('should handle unicode characters in local part', () => { /* ... */ });
});

What to Test

DO test:

  • Public API / exported functions
  • Business logic and domain rules
  • Edge cases and boundary values
  • Error handling and failure modes
  • User interactions (clicks, inputs, submissions)
  • State transitions
  • Data transformations

DON'T test:

  • Implementation details (private methods, internal state)
  • Third-party library internals
  • Trivial code (simple getters/setters)
  • Framework behavior
  • CSS classes or DOM structure (test behavior, not markup)

Edge Cases to Cover

For every function, consider:

CategoryTest Values
Emptynull, undefined, "", [], {}
Boundary0, -1, Number.MAX_SAFE_INTEGER, Number.MIN_SAFE_INTEGER
Type confusionString where number expected, array where object expected
UnicodeEmoji, CJK characters, RTL text, zero-width spaces
ConcurrencyRapid successive calls, race conditions
NetworkTimeout, 404, 500, empty response, malformed JSON
DatesMidnight, DST transitions, leap years, timezone boundaries

Step 4: Testing Patterns by Category

Pure Functions

describe('formatCurrency', () => {
 it('should format positive amounts', () => {
 expect(formatCurrency(1234.5)).toBe('$1,234.50');
 });

 it('should handle zero', () => {
 expect(formatCurrency(0)).toBe('$0.00');
 });

 it('should handle negative amounts', () => {
 expect(formatCurrency(-100)).toBe('-$100.00');
 });

 it('should handle very large numbers', () => {
 expect(formatCurrency(999999999.99)).toBe('$999,999,999.99');
 });
});

Async Functions

describe('fetchUser', () => {
 it('should return user data for valid id', async () => {
 const user = await fetchUser('123');
 expect(user).toEqual({ id: '123', name: 'John Doe' });
 });

 it('should throw for non-existent user', async () => {
 await expect(fetchUser('invalid')).rejects.toThrow('User not found');
 });

 it('should handle network timeout', async () => {
 vi.useFakeTimers(); // or jest.useFakeTimers()
 const promise = fetchUser('123');
 vi.advanceTimersByTime(30000);
 await expect(promise).rejects.toThrow('timeout');
 vi.useRealTimers();
 });
});

Mocking

// Vitest
import { vi } from 'vitest';
vi.mock('./emailService', () => ({
 sendEmail: vi.fn().mockResolvedValue({ success: true }),
}));

// Jest
jest.mock('./emailService');

// MSW (API mocking — preferred for HTTP)
import { http, HttpResponse } from 'msw';
import { setupServer } from 'msw/node';

const server = setupServer(
 http.get('/api/users/:id', ({ params }) => {
 return HttpResponse.json({ id: params.id, name: 'Test User' });
 })
);

beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

React Components (Testing Library)

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

describe('LoginForm', () => {
 it('should submit with valid credentials', async () => {
 const onSubmit = vi.fn();
 const user = userEvent.setup();

 render(<LoginForm onSubmit={onSubmit} />);

 await user.type(screen.getByLabelText(/email/i), '[email protected]');
 await user.type(screen.getByLabelText(/password/i), 'password123');
 await user.click(screen.getByRole('button', { name: /sign in/i }));

 await waitFor(() => {
 expect(onSubmit).toHaveBeenCalledWith({
 email: '[email protected]',
 password: 'password123',
 });
 });
 });

 it('should show validation error for invalid email', async () => {
 const user = userEvent.setup();
 render(<LoginForm onSubmit={vi.fn()} />);

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

 expect(screen.getByText(/valid email/i)).toBeInTheDocument();
 });

 it('should disable submit button while loading', () => {
 render(<LoginForm onSubmit={vi.fn()} isLoading />);
 expect(screen.getByRole('button', { name: /sign in/i })).toBeDisabled();
 });
});

Custom Hooks

import { renderHook, act, waitFor } from '@testing-library/react';

describe('useCounter', () => {
 it('should start with initial value', () => {
 const { result } = renderHook(() => useCounter(10));
 expect(result.current.count).toBe(10);
 });

 it('should increment', () => {
 const { result } = renderHook(() => useCounter(0));
 act(() => result.current.increment());
 expect(result.current.count).toBe(1);
 });

 it('should not go below zero', () => {
 const { result } = renderHook(() => useCounter(0));
 act(() => result.current.decrement());
 expect(result.current.count).toBe(0);
 });
});

API Route Handlers (Next.js / Node)

describe('POST /api/users', () => {
 it('should create user with valid data', async () => {
 const req = new Request('http://localhost/api/users', {
 method: 'POST',
 body: JSON.stringify({ name: 'Test', email: '[email protected]' }),
 });

 const response = await POST(req);
 const data = await response.json();

 expect(response.status).toBe(201);
 expect(data).toMatchObject({ name: 'Test', email: '[email protected]' });
 });

 it('should return 422 for invalid email', async () => {
 const req = new Request('http://localhost/api/users', {
 method: 'POST',
 body: JSON.stringify({ name: 'Test', email: 'invalid' }),
 });

 const response = await POST(req);
 expect(response.status).toBe(422);
 });
});

Step 5: Test Data Management

Use Factories

const createUser = (overrides: Partial<User> = {}): User => ({
 id: crypto.randomUUID(),
 name: 'Test User',
 email: '[email protected]',
 role: 'user',
 createdAt: new Date('2024-01-01'),
 ...overrides,
});

// Usage
const admin = createUser({ role: 'admin' });
const inactive = createUser({ status: 'inactive', name: 'Inactive User' });

Use Meaningful Data

// Bad — meaningless
const data = { a: 'b', c: 'd' };

// Good — realistic and descriptive
const user = { name: 'Jane Doe', email: '[email protected]', role: 'admin' };

Further reading

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.