Why react-spec-gen?
Built for teams that want consistent test and story coverage — without writing the same boilerplate twice.
Zero boilerplate
One command analyzes your component AST and emits a Vitest test plus a Storybook CSF 3 story right next to the source.
Context-aware inference
Extracts props from inline types, type aliases, interface, forwardRef and memo wrappers — then infers realistic prop values.
Offline & deterministic
No network calls by default. Reproducible output you can trust in CI and review on every run.
AI enhancement (preview)
Opt in with --ai to try the enhancer pipeline. Today it ships a deterministic mock provider — real LLM providers (Anthropic, Ollama) are on the roadmap.
Real testing patterns
Implicit ARIA role mapping, edge cases for booleans, optional props, union variants, and event-handler coverage out of the box.
Programmatic API
Use run() directly from Node to integrate with codegen pipelines, monorepo scripts, or custom tooling.
From component to test + story in one command
react-spec-gen statically analyzes your React component, infers realistic prop values, and writes a Vitest test file and a Storybook CSF 3 story right next to the source.
Your component
// src/components/Button.tsx
type ButtonProps = {
label: string;
variant?: "primary" | "secondary";
disabled?: boolean;
onClick?: () => void;
};
export function Button({ label, variant = "primary", disabled, onClick }: ButtonProps) {
return (
<button disabled={disabled} onClick={onClick}>
{label}
</button>
);
}
Generated test
// Button.test.tsx — generated by react-spec-gen
import { describe, it, expect, vi } from "vitest";
import { render, screen } from "@testing-library/react";
import { Button } from "./Button";
describe("Button", () => {
const baseProps = {
label: "Label",
variant: "primary" as const,
disabled: false,
onClick: vi.fn(),
};
it("renders with default props", () => {
render(<Button {...baseProps} />);
expect(screen.getByRole("button")).toBeDefined();
});
it("when disabled is true", () => {
const props = { ...baseProps, disabled: true };
render(<Button {...props} />);
});
it("when variant is secondary", () => {
const props = { ...baseProps, variant: "secondary" as const };
render(<Button {...props} />);
});
});
Generated story
// Button.stories.tsx — generated by react-spec-gen
import type { Meta, StoryObj } from "@storybook/react";
import { Button } from "./Button";
const meta: Meta<typeof Button> = {
title: "Components/Button",
component: Button,
};
export default meta;
type Story = StoryObj<typeof Button>;
export const Default: Story = {
args: { label: "Label", variant: "primary" },
};
export const Secondary: Story = {
args: { label: "Label", variant: "secondary" },
};
export const Disabled: Story = {
args: { label: "Label", disabled: true },
};
Ready to remove test boilerplate?
Run it once on a component. Review the verification checklist. Commit.