Skip to main content
skill CX Codex

unit-test-generator — Claude Code skill: what it does, how to install

Generates unit test suites using the Arrange-Act-Assert pattern for functions, classes, and React components. Useful when you need test coverage for edge cases and errors.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add patricio0312rev/skills --skill unit-test-generator -a claude-code
Open on GitHub

What unit-test-generator does

The skill writes unit test suites structured around the Arrange-Act-Assert pattern. It mirrors your existing directory layout. Test cases cover normal execution paths, boundary values, and thrown errors.

It includes templates for plain functions, stateful classes, and React components. For asynchronous routines, the skill stubs fetch calls and resets mocks inside afterEach hooks. React component tests rely on @testing-library/react to inspect DOM output and simulate user clicks.

Because test syntax targets Vitest and Jest, your workspace needs one of those runners installed beforehand. The skill only generates test code and cannot execute tests on its own.

What it helps with

  • generate unit tests following the Arrange-Act-Assert pattern
  • test edge cases and expected exceptions
  • mock fetch calls in asynchronous services
  • verify class method logic and state changes
  • test React component rendering and click events

Example prompts for the agent

  • Write unit tests for divide in src/utils/calculator.ts covering division by zero.
  • Generate tests for Counter.tsx verifying initial render and button clicks.
  • Create tests for fetchUser in src/services/userService.ts mocking network responses.

How to install unit-test-generator

1Skills CLI (Vercel)

npx skills add patricio0312rev/skills --skill unit-test-generator -a claude-code

2Manually: put the files in place

~/.claude/skills/unit-test-generator/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/unit-test-generator/

Step by step, with commands for each platform: How to install skills in Claude Code: five ways

Questions about unit-test-generator

How to install unit-test-generator in Claude Code?

Run npx skills add patricio0312rev/skills --skill unit-test-generator -a claude-code in your terminal.

What does unit-test-generator do?

It creates unit test suites following the Arrange-Act-Assert pattern with coverage for edge cases, error scenarios, and normal paths. Generated files replicate your source directory structure.

Which test runners and libraries are supported?

The templates output code for Vitest and Jest, using @testing-library/react when testing React components.

Source text

SKILL.md · MIT
---
name: unit-test-generator
description: Generates comprehensive unit tests with AAA pattern (Arrange-Act-Assert), edge cases, error scenarios, and coverage analysis. Creates test files matching source structure with complete test suites. Use for "unit testing", "test generation", "Jest tests", or "test coverage".
---

# Unit Test Generator

Generate comprehensive unit tests with edge cases and AAA pattern.

## AAA Pattern Template

```typescript
// tests/utils/validator.test.ts
import { describe, it, expect } from "vitest";
import { validateEmail } from "@/utils/validator";

describe("validateEmail", () => {
  it("should return true for valid email", () => {
    // Arrange
    const email = "[email protected]";

    // Act
    const result = validateEmail(email);

    // Assert
    expect(result).toBe(true);
  });

  it("should return false for invalid email - missing @", () => {
    // Arrange
    const email = "userexample.com";

    // Act
    const result = validateEmail(email);

    // Assert
    expect(result).toBe(false);
  });

  it("should return false for invalid email - missing domain", () => {
    // Arrange
    const email = "user@";

    // Act
    const result = validateEmail(email);

    // Assert
    expect(result).toBe(false);
  });
});
```

## Comprehensive Test Cases

```typescript
// src/utils/calculator.ts
export function divide(a: number, b: number): number {
  if (b === 0) {
    throw new Error("Division by zero");
  }
  return a / b;
}

// tests/utils/calculator.test.ts
describe("divide", () => {
  describe("happy path", () => {
    it("should divide positive numbers", () => {
      expect(divide(10, 2)).toBe(5);
    });

    it("should divide negative numbers", () => {
      expect(divide(-10, 2)).toBe(-5);
      expect(divide(10, -2)).toBe(-5);
      expect(divide(-10, -2)).toBe(5);
    });

    it("should handle decimal results", () => {
      expect(divide(10, 3)).toBeCloseTo(3.333, 3);
    });
  });

  describe("edge cases", () => {
    it("should handle zero dividend", () => {
      expect(divide(0, 5)).toBe(0);
    });

    it("should handle very large numbers", () => {
      expect(divide(Number.MAX_SAFE_INTEGER, 2)).toBe(
        Number.MAX_SAFE_INTEGER / 2
      );
    });

    it("should handle very small numbers", () => {
      expect(divide(0.0001, 0.0001)).toBe(1);
    });
  });

  describe("error cases", () => {
    it("should throw error when dividing by zero", () => {
      expect(() => divide(10, 0)).toThrow("Division by zero");
    });

    it("should throw error when dividing by negative zero", () => {
      expect(() => divide(10, -0)).toThrow("Division by zero");
    });
  });
});
```

## Async Function Testing

```typescript
// src/services/userService.ts
export async function fetchUser(id: string): Promise<User> {
  const response = await fetch(`/api/users/${id}`);

  if (!response.ok) {
    throw new Error(`User not found: ${id}`);
  }

  return response.json();
}

// tests/services/userService.test.ts
describe("fetchUser", () => {
  beforeEach(() => {
    global.fetch = vi.fn();
  });

  afterEach(() => {
    vi.resetAllMocks();
  });

  it("should fetch user successfully", async () => {
    // Arrange
    const mockUser = { id: "123", name: "John" };
    (global.fetch as any).mockResolvedValueOnce({
      ok: true,
      json: async () => mockUser,
    });

    // Act
    const user = await fetchUser("123");

    // Assert
    expect(user).toEqual(mockUser);
    expect(global.fetch).toHaveBeenCalledWith("/api/users/123");
  });

  it("should throw error when user not found", async () => {
    // Arrange
    (global.fetch as any).mockResolvedValueOnce({
      ok: false,
    });

    // Act & Assert
    await expect(fetchUser("999")).rejects.toThrow("User not found: 999");
  });

  it("should handle network error", async () => {
    // Arrange
    (global.fetch as any).mockRejectedValueOnce(new Error("Network error"));

    // Act & Assert
    await expect(fetchUser("123")).rejects.toThrow("Network error");
  });
});
```

## Testing Classes

```typescript
// src/models/ShoppingCart.ts
export class ShoppingCart {
  private items: CartItem[] = [];

  add(item: CartItem): void {
    this.items.push(item);
  }

  remove(itemId: string): void {
    this.items = this.items.filter((i) => i.id !== itemId);
  }

  getTotal(): number {
    return this.items.reduce(
      (sum, item) => sum + item.price * item.quantity,
      0
    );
  }

  clear(): void {
    this.items = [];
  }
}

// tests/models/ShoppingCart.test.ts
describe("ShoppingCart", () => {
  let cart: ShoppingCart;

  beforeEach(() => {
    cart = new ShoppingCart();
  });

  describe("add", () => {
    it("should add item to cart", () => {
      // Arrange
      const item = { id: "1", name: "Product", price: 10, quantity: 1 };

      // Act
      cart.add(item);

      // Assert
      expect(cart.getTotal()).toBe(10);
    });

    it("should add multiple items", () => {
      // Arrange
      const item1 = { id: "1", name: "Product 1", price: 10, quantity: 1 };
      const item2 = { id: "2", name: "Product 2", price: 20, quantity: 2 };

      // Act
      cart.add(item1);
      cart.add(item2);

      // Assert
      expect(cart.getTotal()).toBe(50); // 10 + (20 * 2)
    });
  });

  describe("remove", () => {
    it("should remove item from cart", () => {
      // Arrange
      const item = { id: "1", name: "Product", price: 10, quantity: 1 };
      cart.add(item);

      // Act
      cart.remove("1");

      // Assert
      expect(cart.getTotal()).toBe(0);
    });

    it("should not throw when removing non-existent item", () => {
      // Act & Assert
      expect(() => cart.remove("999")).not.toThrow();
    });
  });

  describe("getTotal", () => {
    it("should return 0 for empty cart", () => {
      expect(cart.getTotal()).toBe(0);
    });

    it("should calculate total with quantities", () => {
      // Arrange
      cart.add({ id: "1", name: "Product", price: 10, quantity: 3 });

      // Assert
      expect(cart.getTotal()).toBe(30);
    });
  });

  describe("clear", () => {
    it("should remove all items", () => {
      // Arrange
      cart.add({ id: "1", name: "Product 1", price: 10, quantity: 1 });
      cart.add({ id: "2", name: "Product 2", price: 20, quantity: 1 });

      // Act
      cart.clear();

      // Assert
      expect(cart.getTotal()).toBe(0);
    });
  });
});
```

## Testing React Components

```typescript
// src/components/Counter.tsx
export function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <button onClick={() => setCount(0)}>Reset</button>
    </div>
  );
}

// tests/components/Counter.test.tsx
import { render, screen, fireEvent } from "@testing-library/react";

describe("Counter", () => {
  it("should render with initial count of 0", () => {
    // Arrange & Act
    render(<Counter />);

    // Assert
    expect(screen.getByText("Count: 0")).toBeInTheDocument();
  });

  it("should increment count when button clicked", () => {
    // Arrange
    render(<Counter />);
    const button = screen.getByText("Increment");

    // Act
    fireEvent.click(button);

    // Assert
    expect(screen.getByText("Count: 1")).toBeInTheDocument();
  });

  it("should increment multiple times", () => {
    // Arrange
    render(<Counter />);
    const button = screen.getByText("Increment");

    // Act
    fireEvent.click(button);
    fireEvent.click(button);
    fireEvent.click(button);

    // Assert
    expect(screen.getByText("Count: 3")).toBeInTheDocument();
  });

  it("should reset count to 0", () => {
    // Arrange
    render(<Counter />);
    fireEvent.click(screen.getByText("Increment"));

    // Act
    fireEvent.click(screen.getByText("Reset"));

    // Assert
    expect(screen.getByText("Count: 0")).toBeInTheDocument();
  });
});
```

## Edge Case Categories

```typescript
// Test case generation template
interface TestCase {
  category: "happy-path" | "edge-case" | "error-case";
  description: string;
  input: any;
  expectedOutput: any;
}

const testCases: TestCase[] = [
  // Happy path
  {
    category: "happy-path",
    description: "typical valid input",
    input: "[email protected]",
    expectedOutput: true,
  },

  // Edge cases
  {
    category: "edge-case",
    description: "empty string",
    input: "",
    expectedOutput: false,
  },
  {
    category: "edge-case",
    description: "null input",
    input: null,
    expectedOutput: false,
  },
  {
    category: "edge-case",
    description: "undefined input",
    input: undefined,
    expectedOutput: false,
  },
  {
    category: "edge-case",
    description: "whitespace only",
    input: "   ",
    expectedOutput: false,
  },
  {
    category: "edge-case",
    description: "very long email",
    input: "a".repeat(1000) + "@example.com",
    expectedOutput: false,
  },

  // Error cases
  {
    category: "error-case",
    description: "invalid format - no @",
    input: "userexample.com",
    expectedOutput: false,
  },
  {
    category: "error-case",
    description: "invalid format - multiple @",
    input: "user@@example.com",
    expectedOutput: false,
  },
];
```

## Coverage Notes

```typescript
/**
 * Coverage targets for this module:
 *
 * - Line coverage: 100% (all lines executed)
 * - Branch coverage: 100% (all if/else paths tested)
 * - Function coverage: 100% (all functions called)
 * - Statement coverage: 100% (all statements executed)
 *
 * Untested scenarios (intentionally):
 * - None - module is fully covered
 *
 * High-risk areas requiring extra attention:
 * - Division by zero handling
 * - Null/undefined input handling
 * - Type coercion edge cases
 */
```

## Test File Structure

```
src/
  utils/
    validator.ts
    calculator.ts
  services/
    userService.ts
  models/
    ShoppingCart.ts
  components/
    Counter.tsx

tests/
  utils/
    validator.test.ts
    calculator.test.ts
  services/
    userService.test.ts
  models/
    ShoppingCart.test.ts
  components/
    Counter.test.tsx
```

## Test Generation Script

```typescript
// scripts/generate-tests.ts
import * as fs from "fs";
import * as path from "path";

function generateTestTemplate(filePath: string): string {
  const fileName = path.basename(filePath, path.extname(filePath));
  const className = fileName.charAt(0).toUpperCase() + fileName.slice(1);

  return `
import { describe, it, expect } from 'vitest';
import { ${className} } from '@/${filePath}';

describe('${className}', () => {
  describe('happy path', () => {
    it('should handle typical case', () => {
      // Arrange
      const input = /* TODO */;

      // Act
      const result = ${className}(input);

      // Assert
      expect(result).toBe(/* TODO */);
    });
  });

  describe('edge cases', () => {
    it('should handle null input', () => {
      // Arrange
      const input = null;

      // Act & Assert
      expect(() => ${className}(input)).toThrow();
    });

    it('should handle empty input', () => {
      // Arrange
      const input = '';

      // Act
      const result = ${className}(input);

      // Assert
      expect(result).toBe(/* TODO */);
    });
  });

  describe('error cases', () => {
    it('should throw error for invalid input', () => {
      // Arrange
      const input = /* TODO */;

      // Act & Assert
      expect(() => ${className}(input)).toThrow('Invalid input');
    });
  });
});
  `.trim();
}
```

## Best Practices

1. **AAA pattern**: Arrange-Act-Assert structure
2. **One assertion per test**: Keep tests focused
3. **Descriptive names**: "should [expected behavior] when [condition]"
4. **Test edge cases**: null, undefined, empty, max values
5. **Test errors**: Verify error handling
6. **Isolated tests**: No shared state between tests
7. **Fast tests**: Unit tests should run in milliseconds

## Output Checklist

- [ ] Test file created matching source structure
- [ ] AAA pattern used consistently
- [ ] Happy path cases covered
- [ ] Edge cases identified and tested
- [ ] Error cases tested
- [ ] Async functions tested with proper awaits
- [ ] Mocks used for dependencies
- [ ] Coverage notes documented
- [ ] Descriptive test names
- [ ] Setup/teardown hooks used appropriately

© patricio0312rev · MIT · patricio0312rev/skills

Similar skills

All skills for Testing and QA →

From the same set36

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor