用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/Dev-Toolbelt/dev-team-agents --skill frontend-hook-tests命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
基于 SOC 职业分类
正在显示 SKILL.md
| name | frontend-hook-tests |
| description | Testing React hooks and Vue composables in isolation — renderHook, withSetup, async flows. |
When business logic lives in a custom hook (React) or composable (Vue), test it directly — not implicitly through a wrapping component. Component tests verify rendering; hook tests verify logic.
| Signal | Framework | Go to |
|---|---|---|
react in package.json; files under hooks/; functions named use* returning values | React | React |
vue in package.json; files under composables/; functions named use* returning refs | Vue | Vue |
| Both present (monorepo, migration in progress) | Both | Read the section matching the file under test only |
| Neither — logic lives in plain functions/classes | — | Skip this skill; test them as plain units |
Do not install or introduce a hook-testing utility for a framework the project does not use.
| The hook… | Test it directly? |
|---|---|
| Holds state transitions, derived values, or branching | Yes |
| Wraps async work (fetch, debounce, polling) | Yes |
| Returns callbacks the UI invokes | Yes |
| Only forwards a library call with no added logic | No — test the consumer instead |
| Only reads a constant or context value | No |
Use renderHook from @testing-library/react (React 18+; older projects may still use @testing-library/react-hooks).
// Arrange
const { result } = renderHook(() => useCartTotal(mockItems));
// Act
act(() => result.current.addItem(newItem));
// Assert
expect(result.current.total).toBe(expectedTotal);
result.current after the act block — result.current is replaced on every render, so never destructure it up front.act(); wrap async ones in await act(async () => …).wrapper option:const { result } = renderHook(() => useSession(), {
wrapper: ({ children }) => <AuthProvider>{children}</AuthProvider>,
});
rerender(newProps) to assert reaction to changed inputs, and unmount() to assert cleanup (subscriptions closed, timers cleared).Composables that call lifecycle hooks or provide/inject need an active component instance. Call them inside a thin withSetup wrapper:
const { count, increment } = withSetup(() => useCounter());
increment();
expect(count.value).toBe(1);
// withSetup — minimal test helper
function withSetup<T>(composable: () => T): T {
let result!: T;
const app = createApp({ setup() { result = composable(); return () => {}; } });
app.mount(document.createElement('div'));
return result;
}
.value for refs and computed values, not on the ref object itself.await nextTick() (or flushPromises()) before asserting on anything that depends on reactivity or a resolved promise.app.unmount() can trigger onUnmounted.act() / await flushPromises() / nextTick() / fake timers. Never rely on an arbitrary setTimeout in the test.skills/testing/decoupled-frontend/SKILL.md.| Anti-pattern | Why it hurts | Do instead |
|---|---|---|
| Rendering a whole component just to reach the hook | Couples logic tests to markup | Render the hook directly |
Destructuring result.current before acting | Captures a stale render | Read result.current.x after each act |
| Asserting immediately after an async call | Passes or fails by timing luck | Await the flush helper first |
| Testing a hook that has no logic | Pure maintenance cost | Delete the test; cover the consumer |
| Duplicating an existing component test at hook level | Double maintenance, no new signal | Pick one layer per behavior |