Install
openclaw skills install automatic-test-generatorAutomatically generate unit tests from functions with comprehensive coverage and edge cases
openclaw skills install automatic-test-generatorAutomatically generates comprehensive unit tests from your existing functions and classes. Analyzes function signatures, implementation details, and edge cases to create thorough test suites that improve code quality and catch bugs before production.
This skill analyzes your code and generates complete, runnable unit tests including:
Supports multiple testing frameworks:
Writing tests is time-consuming and often postponed:
Studies show code with comprehensive tests has:
Use this skill whenever you need tests:
For simple functions:
For complex functions:
For classes:
Your code:
// src/utils/string.ts
export function capitalize(str: string): string {
if (!str) return '';
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
}
Ask Claude:
"Generate unit tests for the capitalize function in src/utils/string.ts"
Generated tests:
// src/utils/string.test.ts
import { capitalize } from './string';
describe('capitalize', () => {
it('should capitalize first letter of lowercase string', () => {
expect(capitalize('hello')).toBe('Hello');
});
it('should capitalize and lowercase rest of string', () => {
expect(capitalize('hELLO')).toBe('Hello');
});
it('should handle single character string', () => {
expect(capitalize('a')).toBe('A');
});
it('should return empty string for empty input', () => {
expect(capitalize('')).toBe('');
});
it('should return empty string for null input', () => {
expect(capitalize(null as any)).toBe('');
});
it('should return empty string for undefined input', () => {
expect(capitalize(undefined as any)).toBe('');
});
it('should handle string with spaces', () => {
expect(capitalize('hello world')).toBe('Hello world');
});
it('should handle string with numbers', () => {
expect(capitalize('123abc')).toBe('123abc');
});
it('should handle already capitalized string', () => {
expect(capitalize('Hello')).toBe('Hello');
});
});
Your code:
// src/api/users.ts
import { db } from './database';
import { cache } from './cache';
export async function getUser(id: string): Promise<User> {
const cached = await cache.get(`user:${id}`);
if (cached) return cached;
const user = await db.users.findById(id);
if (!user) throw new Error('User not found');
await cache.set(`user:${id}`, user, { ttl: 300 });
return user;
}
Ask Claude:
"Generate Jest tests for getUser function with proper mocking"
Generated tests:
// src/api/users.test.ts
import { getUser } from './users';
import { db } from './database';
import { cache } from './cache';
jest.mock('./database');
jest.mock('./cache');
describe('getUser', () => {
const mockUser = { id: '123', name: 'John Doe', email: 'john@example.com' };
beforeEach(() => {
jest.clearAllMocks();
});
it('should return cached user if available', async () => {
(cache.get as jest.Mock).mockResolvedValue(mockUser);
const result = await getUser('123');
expect(result).toEqual(mockUser);
expect(cache.get).toHaveBeenCalledWith('user:123');
expect(db.users.findById).not.toHaveBeenCalled();
});
it('should fetch from database when cache misses', async () => {
(cache.get as jest.Mock).mockResolvedValue(null);
(db.users.findById as jest.Mock).mockResolvedValue(mockUser);
const result = await getUser('123');
expect(result).toEqual(mockUser);
expect(cache.get).toHaveBeenCalledWith('user:123');
expect(db.users.findById).toHaveBeenCalledWith('123');
});
it('should cache user after database fetch', async () => {
(cache.get as jest.Mock).mockResolvedValue(null);
(db.users.findById as jest.Mock).mockResolvedValue(mockUser);
await getUser('123');
expect(cache.set).toHaveBeenCalledWith('user:123', mockUser, { ttl: 300 });
});
it('should throw error when user not found', async () => {
(cache.get as jest.Mock).mockResolvedValue(null);
(db.users.findById as jest.Mock).mockResolvedValue(null);
await expect(getUser('999')).rejects.toThrow('User not found');
});
it('should not cache when user not found', async () => {
(cache.get as jest.Mock).mockResolvedValue(null);
(db.users.findById as jest.Mock).mockResolvedValue(null);
await expect(getUser('999')).rejects.toThrow();
expect(cache.set).not.toHaveBeenCalled();
});
it('should handle cache errors gracefully', async () => {
(cache.get as jest.Mock).mockRejectedValue(new Error('Cache unavailable'));
(db.users.findById as jest.Mock).mockResolvedValue(mockUser);
// Should fallback to database when cache fails
const result = await getUser('123');
expect(result).toEqual(mockUser);
});
});
Your code:
// src/services/counter.ts
export class Counter {
private count: number = 0;
increment(): number {
this.count++;
return this.count;
}
decrement(): number {
this.count--;
return this.count;
}
reset(): void {
this.count = 0;
}
getValue(): number {
return this.count;
}
}
Ask Claude:
"Generate tests for Counter class"
Generated tests:
// src/services/counter.test.ts
import { Counter } from './counter';
describe('Counter', () => {
let counter: Counter;
beforeEach(() => {
counter = new Counter();
});
describe('increment', () => {
it('should increment counter by 1', () => {
expect(counter.increment()).toBe(1);
});
it('should increment multiple times correctly', () => {
counter.increment();
counter.increment();
expect(counter.increment()).toBe(3);
});
it('should return current value after increment', () => {
const result = counter.increment();
expect(result).toBe(counter.getValue());
});
});
describe('decrement', () => {
it('should decrement counter by 1', () => {
counter.increment(); // Set to 1 first
expect(counter.decrement()).toBe(0);
});
it('should allow negative values', () => {
expect(counter.decrement()).toBe(-1);
});
it('should decrement multiple times correctly', () => {
counter.decrement();
counter.decrement();
expect(counter.decrement()).toBe(-3);
});
});
describe('reset', () => {
it('should reset counter to 0', () => {
counter.increment();
counter.increment();
counter.reset();
expect(counter.getValue()).toBe(0);
});
it('should reset from negative values', () => {
counter.decrement();
counter.reset();
expect(counter.getValue()).toBe(0);
});
});
describe('getValue', () => {
it('should return initial value of 0', () => {
expect(counter.getValue()).toBe(0);
});
it('should return current value after operations', () => {
counter.increment();
counter.increment();
counter.decrement();
expect(counter.getValue()).toBe(1);
});
});
describe('integration', () => {
it('should handle complex operation sequences', () => {
counter.increment(); // 1
counter.increment(); // 2
counter.decrement(); // 1
counter.increment(); // 2
counter.reset(); // 0
counter.decrement(); // -1
expect(counter.getValue()).toBe(-1);
});
});
});
Your code:
// src/api/posts.ts
export async function createPost(title: string, content: string, authorId: string): Promise<Post> {
if (!title || title.trim().length === 0) {
throw new Error('Title is required');
}
if (!content || content.length < 10) {
throw new Error('Content must be at least 10 characters');
}
if (!authorId) {
throw new Error('Author ID is required');
}
const post = await db.posts.create({
title: title.trim(),
content,
authorId,
createdAt: new Date()
});
return post;
}
Ask Claude:
"Generate comprehensive tests for createPost with all validation cases"
Generated tests:
// src/api/posts.test.ts
import { createPost } from './posts';
import { db } from './database';
jest.mock('./database');
describe('createPost', () => {
const validTitle = 'Test Post';
const validContent = 'This is valid content with enough characters';
const validAuthorId = 'author-123';
beforeEach(() => {
jest.clearAllMocks();
(db.posts.create as jest.Mock).mockResolvedValue({
id: 'post-123',
title: validTitle,
content: validContent,
authorId: validAuthorId,
createdAt: expect.any(Date)
});
});
describe('validation', () => {
it('should throw error when title is empty', async () => {
await expect(createPost('', validContent, validAuthorId))
.rejects.toThrow('Title is required');
});
it('should throw error when title is only whitespace', async () => {
await expect(createPost(' ', validContent, validAuthorId))
.rejects.toThrow('Title is required');
});
it('should throw error when title is null', async () => {
await expect(createPost(null as any, validContent, validAuthorId))
.rejects.toThrow('Title is required');
});
it('should throw error when content is empty', async () => {
await expect(createPost(validTitle, '', validAuthorId))
.rejects.toThrow('Content must be at least 10 characters');
});
it('should throw error when content is less than 10 characters', async () => {
await expect(createPost(validTitle, 'short', validAuthorId))
.rejects.toThrow('Content must be at least 10 characters');
});
it('should throw error when authorId is missing', async () => {
await expect(createPost(validTitle, validContent, ''))
.rejects.toThrow('Author ID is required');
});
it('should throw error when authorId is null', async () => {
await expect(createPost(validTitle, validContent, null as any))
.rejects.toThrow('Author ID is required');
});
});
describe('success cases', () => {
it('should create post with valid data', async () => {
const result = await createPost(validTitle, validContent, validAuthorId);
expect(db.posts.create).toHaveBeenCalledWith({
title: validTitle,
content: validContent,
authorId: validAuthorId,
createdAt: expect.any(Date)
});
expect(result).toHaveProperty('id');
});
it('should trim whitespace from title', async () => {
await createPost(' Trimmed Title ', validContent, validAuthorId);
expect(db.posts.create).toHaveBeenCalledWith(
expect.objectContaining({ title: 'Trimmed Title' })
);
});
it('should accept content exactly 10 characters', async () => {
const result = await createPost(validTitle, '1234567890', validAuthorId);
expect(result).toBeDefined();
});
it('should accept very long content', async () => {
const longContent = 'a'.repeat(10000);
const result = await createPost(validTitle, longContent, validAuthorId);
expect(result).toBeDefined();
});
});
describe('database integration', () => {
it('should handle database errors', async () => {
(db.posts.create as jest.Mock).mockRejectedValue(new Error('Database error'));
await expect(createPost(validTitle, validContent, validAuthorId))
.rejects.toThrow('Database error');
});
it('should include timestamp in created post', async () => {
const beforeCreate = new Date();
await createPost(validTitle, validContent, validAuthorId);
const afterCreate = new Date();
const createCall = (db.posts.create as jest.Mock).mock.calls[0][0];
expect(createCall.createdAt.getTime()).toBeGreaterThanOrEqual(beforeCreate.getTime());
expect(createCall.createdAt.getTime()).toBeLessThanOrEqual(afterCreate.getTime());
});
});
});
"Generate Jest tests for this function"
"Create Vitest tests for this component"
"Write Pytest tests for this Python function"
"Generate Go tests for this package"
"Generate basic tests with happy path only"
"Create comprehensive tests with all edge cases"
"Write tests for error scenarios only"
"Generate tests with Jest mocks for dependencies"
"Create tests using manual mocks"
"Write tests without mocking (integration style)"
After generation, verify:
# package.json
{
"scripts": {
"test": "jest",
"test:coverage": "jest --coverage",
"test:watch": "jest --watch"
}
}
# Enforce coverage thresholds
# jest.config.js
module.exports = {
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80
}
}
};
Cause: Implementation has bugs or generated assertions are incorrect.
Solution:
npm testCause: Simple function with obvious behavior.
Solution: Ask for more comprehensive tests:
"Generate more edge case tests for this function"
"Add error scenario tests"
"Include boundary value tests"
Cause: Claude didn't detect all dependencies.
Solution: Specify what to mock:
"Generate tests and mock the 'database' and 'logger' modules"
Cause: Mock types don't match original.
Solution:
as jest.Mock type assertions@types/jest or @types/vitestjest.MockedFunction<typeof fn> for better typesGenerate test data factories:
// Generated test builders
function buildUser(overrides?: Partial<User>): User {
return {
id: '123',
name: 'Test User',
email: 'test@example.com',
...overrides
};
}
For complex output:
it('should generate correct user profile', () => {
const result = generateProfile(mockUser);
expect(result).toMatchSnapshot();
});
For multiple similar cases:
describe.each([
['hello', 'Hello'],
['WORLD', 'World'],
['tEsT', 'Test'],
])('capitalize(%s)', (input, expected) => {
it(`should return ${expected}`, () => {
expect(capitalize(input)).toBe(expected);
});
});
Generate and review coverage:
npm test -- --coverage
# View HTML report
open coverage/lcov-report/index.html
it('should do something', () => {
// Arrange: Setup test data
const input = 'test';
// Act: Execute function
const result = doSomething(input);
// Assert: Verify result
expect(result).toBe('expected');
});
it('should format user name when user has full name', () => {
// Given: a user with first and last name
const user = { firstName: 'John', lastName: 'Doe' };
// When: formatting the name
const result = formatUserName(user);
// Then: should return full name
expect(result).toBe('John Doe');
});
// src/utils/math.ts
export function divide(a: number, b: number): number {
return a / b;
}
// ❌ No tests, bugs in production:
// divide(10, 0) => Infinity (should throw error)
// divide(null, 5) => NaN (should validate input)
// Tests catch bugs during development
describe('divide', () => {
it('should divide two positive numbers', () => {
expect(divide(10, 2)).toBe(5);
});
it('should throw error when dividing by zero', () => {
expect(() => divide(10, 0)).toThrow('Cannot divide by zero');
});
it('should validate input types', () => {
expect(() => divide(null as any, 5)).toThrow('Invalid input');
});
// Bug found! Implementation needs fixing.
});
Industry standards:
Focus on:
While this skill helps write tests after code, consider using it to learn TDD:
This reverses typical workflow but teaches TDD patterns.
Pro Tip: Use this skill to learn testing patterns, then gradually write tests yourself as you internalize best practices. The goal is test coverage, not dependency on automation!
License: MIT-0 (Public Domain)