Skip to content

Commit 2ffeb0f

Browse files
devmgnclaude
andauthored
feat: add msw-auto-mock for OpenAPI-based mock generation (#2668)
* feat: add msw-auto-mock for OpenAPI-based mock generation OpenAPI spec から MSW ハンドラーを自動生成する msw-auto-mock を導入し、 Storybook の Story と API テストを追加。 - msw-auto-mock + @faker-js/faker を devDependencies に追加 - generate-mock スクリプト追加(ja ロケール自動適用) - PostList の Storybook Story(Default / ServerError / NetworkError / Empty) - apiClient と post.queries のユニットテスト(MSW server.use パターン) - oxlint / oxfmt の ignore に src/mocks/** を追加 - テスト・Story 向けの unsafe 系ルール緩和 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: update rules for msw-auto-mock integration - commands.md: generate-mock コマンド追加 - mocks.md: msw-auto-mock の運用ルール、ファイル構成表 - storybook.md: MSW Story パターン、inline: false の説明 - api.md: src/mocks/ の手動編集禁止ルール - testing.md: server.use() パターンの具体例 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: rewrite rules in English and simplify Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: lint --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent a9031f3 commit 2ffeb0f

17 files changed

Lines changed: 1170 additions & 61 deletions

File tree

.claude/rules/api.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ paths:
66
# API Layer Rules
77

88
- **Never manually edit** files in `src/api/openapi/` — regenerate with `pnpm generate-api`
9+
- **Never manually edit** files in `src/mocks/` (except `server.ts`) — regenerate with `pnpm generate-mock`
910

1011
## TanStack Query Pattern
1112

.claude/rules/commands.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,6 @@ pnpm storybook # Storybook dev server
2121
pnpm build-storybook # Storybook static build
2222
pnpm generate-api # Generate OpenAPI client
2323
pnpm generate-api:clean # Clean + regenerate OpenAPI client
24+
pnpm generate-mock # Generate MSW handlers from OpenAPI spec
2425
pnpm analyze # Bundle size analysis (requires build)
2526
```

.claude/rules/mocks.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,19 @@ paths:
66
# MSW Mock Rules
77

88
- Worker directory: `./public`
9-
- Vitest: `setupServer()` — shared instance in `src/mocks/server.ts`
9+
- Vitest: shared `setupServer()` in `src/mocks/server.ts`
1010
- Storybook: `mswLoader` in `.storybook/preview.tsx` (NOT `setupWorker()`)
1111
- Test isolation: `server.resetHandlers()` runs in `afterEach` automatically
12+
13+
## msw-auto-mock
14+
15+
- `pnpm generate-mock` generates `src/mocks/handlers.ts` from `openapi.yml`
16+
- Auto-patches to `@faker-js/faker/locale/ja` (Japanese data, deterministic via `faker.seed(1)`)
17+
- **Never manually edit** generated files — re-run `pnpm generate-mock` after spec changes
18+
19+
## Files
20+
21+
| File | Purpose | Managed |
22+
| --------------------------------------------------- | ----------------------------------------------------------- | --------- |
23+
| `handlers.ts`, `browser.ts`, `node.ts`, `native.ts` | Auto-generated from OpenAPI | Generated |
24+
| `server.ts` | Vitest shared instance (empty, use `server.use()` per test) | Manual |

.claude/rules/storybook.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,33 @@ type Story = StoryObj<typeof ComponentName>;
3232
export const Default: Story = {};
3333
```
3434

35+
## MSW Story Pattern
36+
37+
Use `parameters.msw.handlers` with auto-generated or custom handlers. Set `inline: false` when stories for the same endpoint return different responses (required for Docs page MSW isolation).
38+
39+
```typescript
40+
const meta = {
41+
component: PostList,
42+
parameters: {
43+
docs: { story: { inline: false } },
44+
msw: { handlers },
45+
},
46+
} satisfies Meta<typeof PostList>;
47+
48+
export const ServerError: Story = {
49+
parameters: {
50+
docs: { story: { inline: false } },
51+
msw: {
52+
handlers: [
53+
http.get("https://...", () =>
54+
HttpResponse.json({ message: "Error" }, { status: 500 }),
55+
),
56+
],
57+
},
58+
},
59+
};
60+
```
61+
3562
## MCP Integration
3663

3764
Use `storybook-mcp` tools: `list-all-documentation`, `get-documentation`, `get-documentation-for-story`, `get-storybook-story-instructions`, `preview-stories`, `run-story-tests`.

.claude/rules/testing.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,15 @@ paths:
2121

2222
## API Mocking
2323

24-
Import shared MSW server: `import { server } from "../../mocks/server"` then `server.use(http.get(...))`.
24+
Use shared MSW server with per-test handlers. `resetHandlers()` runs automatically in `afterEach`.
25+
26+
```typescript
27+
import { HttpResponse, http } from "msw";
28+
import { server } from "../../mocks/server";
29+
30+
server.use(
31+
http.get(`${BASE_URL}/posts`, () => HttpResponse.json([...])),
32+
);
33+
```
34+
35+
- Cast `request.json()` as `Record<string, unknown>` when spreading into response

oxfmt.config.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
import { defineConfig } from "oxfmt";
22

33
export default defineConfig({
4-
ignorePatterns: [".*/skills/**", "public/**", "src/api/openapi/**"],
4+
ignorePatterns: [
5+
".*/skills/**",
6+
"public/**",
7+
"src/api/openapi/**",
8+
"src/mocks/**",
9+
],
510
printWidth: 80,
611
experimentalSortImports: {
712
groups: [

oxlint.config.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export default defineConfig({
4242
"out/**",
4343
"public/**",
4444
"src/api/openapi/**",
45+
"src/mocks/**",
4546
"storybook-static/**",
4647
],
4748
categories: {
@@ -185,6 +186,8 @@ export default defineConfig({
185186
"react/rules-of-hooks": "off",
186187
"typescript/consistent-type-assertions": "off",
187188
"typescript/no-explicit-any": "off",
189+
"typescript/no-unsafe-argument": "off",
190+
"typescript/no-unsafe-assignment": "off",
188191

189192
// eslint-plugin-storybook (jsPlugin)
190193
"storybook/await-interactions": "error",
@@ -222,6 +225,7 @@ export default defineConfig({
222225
"eslint/no-undef": "off",
223226
"typescript/consistent-type-assertions": "off",
224227
"typescript/no-explicit-any": "off",
228+
"typescript/no-unsafe-argument": "off",
225229
"typescript/no-unsafe-assignment": "off",
226230
"typescript/no-unsafe-type-assertion": "off",
227231
"vitest/consistent-test-it": ["error", { fn: "it" }],

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@
2323
"storybook": "storybook dev -p 6006",
2424
"build-storybook": "storybook build",
2525
"generate-api": "pnpm dlx @openapitools/openapi-generator-cli generate -i openapi.yml -g typescript-fetch -o ./src/api/openapi -c openapiconfig.json --openapi-normalizer SET_TAGS_FOR_ALL_OPERATIONS=default",
26-
"generate-api:clean": "rm -rf src/api/openapi && pnpm generate-api"
26+
"generate-api:clean": "rm -rf src/api/openapi && pnpm generate-api",
27+
"generate-mock": "msw-auto-mock openapi.yml -o ./src/mocks --typescript --base-url https://jsonplaceholder.typicode.com && sed -i '' 's|from \"@faker-js/faker\"|from \"@faker-js/faker/locale/ja\"|' ./src/mocks/handlers.ts"
2728
},
2829
"dependencies": {
2930
"@hookform/resolvers": "5.2.2",
@@ -45,6 +46,7 @@
4546
"zod": "4.3.6"
4647
},
4748
"devDependencies": {
49+
"@faker-js/faker": "10.4.0",
4850
"@next/env": "16.2.1",
4951
"@storybook/addon-a11y": "10.3.3",
5052
"@storybook/addon-docs": "10.3.3",
@@ -71,6 +73,7 @@
7173
"jest-extended": "7.0.0",
7274
"knip": "6.1.0",
7375
"msw": "2.12.14",
76+
"msw-auto-mock": "0.32.0",
7477
"msw-storybook-addon": "2.0.6",
7578
"oxfmt": "0.42.0",
7679
"oxlint": "1.57.0",

0 commit comments

Comments
 (0)