{"id":"aa928796-6db7-423b-97c2-907de7bc66bc","shortId":"XZgfuN","kind":"skill","title":"testing","tagline":"Auto-activate for test_*.py, *.test.ts, *.spec.ts, conftest.py, vitest.config.ts. Testing with pytest and vitest: fixtures, mocking, coverage, async testing, anyio. Use when: writing or refactoring tests, setting up fixtures/mocks, configuring coverage, or debugging test failures","description":"# Testing Skill\n\n<workflow>\n\n## Python Testing (pytest)\n\n### Basic Test Structure\n\n<example>\n\n```python\nimport pytest\n\n# Function-based tests (preferred over class-based)\ndef test_addition():\n    assert 1 + 1 == 2\n\ndef test_division_by_zero():\n    with pytest.raises(ZeroDivisionError):\n        1 / 0\n\n# Parametrized tests\n@pytest.mark.parametrize(\"input,expected\", [\n    (\"hello\", 5),\n    (\"\", 0),\n    (\"world\", 5),\n])\ndef test_string_length(input: str, expected: int):\n    assert len(input) == expected\n```\n\n</example>\n\n### Async Tests\n\n<example>\n\n```python\nimport pytest\nfrom httpx import AsyncClient\n\n@pytest.mark.anyio\nasync def test_async_endpoint(client: AsyncClient):\n    response = await client.get(\"/api/items\")\n    assert response.status_code == 200\n    assert isinstance(response.json(), list)\n```\n\n</example>\n\n### Fixtures\n\n<example>\n\n```python\nimport pytest\nfrom sqlalchemy.ext.asyncio import AsyncSession\n\n@pytest.fixture\ndef sample_user() -> User:\n    return User(name=\"Test\", email=\"test@example.com\")\n\n@pytest.fixture\nasync def db_session(engine) -> AsyncGenerator[AsyncSession, None]:\n    async with AsyncSession(engine) as session:\n        yield session\n        await session.rollback()\n\n@pytest.fixture(scope=\"module\")\ndef client(app) -> TestClient:\n    return TestClient(app)\n```\n\n</example>\n\n### Mocking\n\n<example>\n\n```python\nfrom unittest.mock import AsyncMock, MagicMock, patch\n\ndef test_with_mock():\n    with patch(\"module.external_api\") as mock_api:\n        mock_api.return_value = {\"status\": \"ok\"}\n        result = function_that_calls_api()\n        assert result[\"status\"] == \"ok\"\n        mock_api.assert_called_once()\n\n@pytest.fixture\ndef mock_service():\n    service = MagicMock(spec=MyService)\n    service.fetch_data = AsyncMock(return_value=[])\n    return service\n```\n\n</example>\n\n### HTTP Testing with Litestar\n\n<example>\n\n```python\nfrom litestar.testing import TestClient\n\ndef test_get_items(client: TestClient):\n    response = client.get(\"/items\")\n    assert response.status_code == 200\n\ndef test_create_item(client: TestClient):\n    response = client.post(\"/items\", json={\"name\": \"Test\"})\n    assert response.status_code == 201\n    assert response.json()[\"name\"] == \"Test\"\n```\n\n</example>\n\n### Coverage\n\n```bash\n# Run with coverage\npytest --cov=src --cov-report=html\n\n# Fail if coverage below threshold\npytest --cov=src --cov-fail-under=90\n```\n\n---\n\n## TypeScript Testing (Vitest)\n\n### Basic Test Structure\n\n<example>\n\n```typescript\nimport { describe, it, expect, beforeEach, afterEach } from 'vitest';\n\ndescribe('Calculator', () => {\n  let calc: Calculator;\n\n  beforeEach(() => {\n    calc = new Calculator();\n  });\n\n  it('should add numbers', () => {\n    expect(calc.add(1, 2)).toBe(3);\n  });\n\n  it('should throw on division by zero', () => {\n    expect(() => calc.divide(1, 0)).toThrow('Division by zero');\n  });\n});\n```\n\n</example>\n\n### Async Tests\n\n<example>\n\n```typescript\nimport { describe, it, expect, vi } from 'vitest';\n\ndescribe('API', () => {\n  it('should fetch users', async () => {\n    const users = await fetchUsers();\n    expect(users).toHaveLength(3);\n  });\n\n  it('should handle errors', async () => {\n    await expect(fetchInvalidEndpoint()).rejects.toThrow();\n  });\n});\n```\n\n</example>\n\n### Mocking\n\n<example>\n\n```typescript\nimport { vi, describe, it, expect, beforeEach } from 'vitest';\n\n// Mock a module\nvi.mock('./api', () => ({\n  fetchUsers: vi.fn(() => Promise.resolve([{ id: 1 }])),\n}));\n\n// Mock specific function\nconst mockFetch = vi.fn();\n\ndescribe('with mocks', () => {\n  beforeEach(() => {\n    vi.clearAllMocks();\n  });\n\n  it('should call API', async () => {\n    mockFetch.mockResolvedValue({ data: [] });\n\n    await doSomething(mockFetch);\n\n    expect(mockFetch).toHaveBeenCalledWith('/api/items');\n  });\n});\n\n// Spy on existing function\nconst spy = vi.spyOn(console, 'log');\n```\n\n</example>\n\n### Testing Components (React)\n\n<example>\n\n```typescript\nimport { render, screen, fireEvent } from '@testing-library/react';\nimport { describe, it, expect } from 'vitest';\n\ndescribe('Button', () => {\n  it('should render with text', () => {\n    render(<Button>Click me</Button>);\n    expect(screen.getByRole('button')).toHaveTextContent('Click me');\n  });\n\n  it('should call onClick', async () => {\n    const onClick = vi.fn();\n    render(<Button onClick={onClick}>Click</Button>);\n\n    await fireEvent.click(screen.getByRole('button'));\n\n    expect(onClick).toHaveBeenCalled();\n  });\n});\n```\n\n</example>\n\n### Testing Components (Vue)\n\n<example>\n\n```typescript\nimport { mount } from '@vue/test-utils';\nimport { describe, it, expect } from 'vitest';\n\ndescribe('Counter', () => {\n  it('should increment', async () => {\n    const wrapper = mount(Counter);\n\n    await wrapper.find('button').trigger('click');\n\n    expect(wrapper.find('.count').text()).toBe('1');\n  });\n});\n```\n\n</example>\n\n### Vitest Configuration\n\n<example>\n\n```typescript\n// vitest.config.ts\nimport { defineConfig } from 'vitest/config';\n\nexport default defineConfig({\n  test: {\n    globals: true,\n    environment: 'jsdom',\n    coverage: {\n      provider: 'v8',\n      reporter: ['text', 'html'],\n      thresholds: {\n        lines: 90,\n      },\n    },\n  },\n});\n```\n\n</example>\n\n</workflow>\n\n<guardrails>\n\n## Best Practices\n\n### Python\n\n- Use function-based tests (not class-based)\n- Use `pytest.mark.anyio` for async tests\n- Use fixtures for setup/teardown\n- Use `@pytest.mark.parametrize` for multiple inputs\n- Target 90%+ coverage on modified modules\n\n### TypeScript\n\n- Use `describe` for grouping related tests\n- Use `beforeEach` to reset state\n- Use `vi.mock` for module mocking\n- Use Testing Library for component tests\n- Prefer user-centric queries (getByRole, getByText)\n\n</guardrails>\n\n## References Index\n\n- **[Async Testing](references/async_testing.md)** - anyio/pytest-anyio setup, async fixtures, context manager testing, and common pitfalls.\n\n## Official References\n\n- <https://docs.pytest.org/en/stable/>\n- <https://docs.pytest.org/en/stable/changelog.html>\n- <https://vitest.dev/guide/>\n- <https://vitest.dev/config/coverage>\n- <https://github.com/vitest-dev/vitest/releases>\n- <https://anyio.readthedocs.io/en/stable/testing.html>\n\n## Shared Styleguide Baseline\n\n- Use shared styleguides for generic language/framework rules to reduce duplication in this skill.\n- [General Principles](https://github.com/cofin/flow/blob/main/templates/styleguides/general.md)\n- [Testing](https://github.com/cofin/flow/blob/main/templates/styleguides/frameworks/testing.md)\n- [Python](https://github.com/cofin/flow/blob/main/templates/styleguides/languages/python.md)\n- [TypeScript](https://github.com/cofin/flow/blob/main/templates/styleguides/languages/typescript.md)\n- Keep this skill focused on tool-specific workflows, edge cases, and integration details.\n\n<validation>\n## Validation\n\nAdd validation instructions here.\n</validation>","tags":["testing","flow","cofin","agent-skills","ai-agents","beads","claude-code","codex","cursor","developer-tools","gemini-cli","opencode"],"capabilities":["skill","source-cofin","skill-testing","topic-agent-skills","topic-ai-agents","topic-beads","topic-claude-code","topic-codex","topic-cursor","topic-developer-tools","topic-gemini-cli","topic-opencode","topic-plugin","topic-slash-commands","topic-spec-driven-development"],"categories":["flow"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/cofin/flow/testing","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"cli":"npx skills add cofin/flow","source_repo":"https://github.com/cofin/flow","install_from":"skills.sh"}},"qualityScore":"0.455","qualityRationale":"deterministic score 0.46 from registry signals: · indexed on github topic:agent-skills · 11 github stars · SKILL.md body (6,857 chars)","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill-github:v1","enrichmentVersion":1,"enrichedAt":"2026-04-24T07:03:20.729Z","embedding":null,"createdAt":"2026-04-23T13:04:02.386Z","updatedAt":"2026-04-24T07:03:20.729Z","lastSeenAt":"2026-04-24T07:03:20.729Z","tsv":"'/api':388 '/api/items':117,418 '/cofin/flow/blob/main/templates/styleguides/frameworks/testing.md)':664 '/cofin/flow/blob/main/templates/styleguides/general.md)':660 '/cofin/flow/blob/main/templates/styleguides/languages/python.md)':668 '/cofin/flow/blob/main/templates/styleguides/languages/typescript.md)':672 '/config/coverage':633 '/en/stable/':624 '/en/stable/changelog.html':627 '/en/stable/testing.html':639 '/guide/':630 '/items':241,254 '/react':440 '/vitest-dev/vitest/releases':636 '0':74,82,335 '1':62,63,73,321,334,393,517 '2':64,322 '200':121,245 '201':261 '3':324,364 '5':81,84 '90':290,542,570 'activ':4 'add':317,688 'addit':60 'aftereach':303 'anyio':22 'anyio.readthedocs.io':638 'anyio.readthedocs.io/en/stable/testing.html':637 'anyio/pytest-anyio':610 'api':189,192,201,351,408 'app':169,173 'assert':61,93,118,122,202,242,258,262 'async':20,97,107,110,146,154,340,356,369,409,467,502,558,607,612 'asynccli':105,113 'asyncgener':151 'asyncmock':179,219 'asyncsess':133,152,156 'auto':3 'auto-activ':2 'await':115,162,359,370,412,476,507 'base':51,57,549,554 'baselin':642 'bash':267 'basic':43,294 'beforeeach':302,311,381,403,583 'best':543 'button':448,459,472,479,509 'calc':309,312 'calc.add':320 'calc.divide':333 'calcul':307,310,314 'call':200,207,407,465 'case':683 'centric':601 'class':56,553 'class-bas':55,552 'click':455,461,475,511 'client':112,168,237,250 'client.get':116,240 'client.post':253 'code':120,244,260 'common':618 'compon':429,484,596 'configur':32,519 'conftest.py':10 'consol':426 'const':357,397,423,468,503 'context':614 'count':514 'counter':498,506 'cov':272,275,284,287 'cov-fail-und':286 'cov-report':274 'coverag':19,33,266,270,280,534,571 'creat':248 'data':218,411 'db':148 'debug':35 'def':58,65,85,108,135,147,167,182,210,233,246 'default':527 'defineconfig':523,528 'describ':299,306,344,350,378,400,442,447,492,497,577 'detail':686 'divis':67,329,337 'docs.pytest.org':623,626 'docs.pytest.org/en/stable/':622 'docs.pytest.org/en/stable/changelog.html':625 'dosometh':413 'duplic':652 'edg':682 'email':143 'endpoint':111 'engin':150,157 'environ':532 'error':368 'exist':421 'expect':79,91,96,301,319,332,346,361,371,380,415,444,457,480,494,512 'export':526 'fail':278,288 'failur':37 'fetch':354 'fetchinvalidendpoint':372 'fetchus':360,389 'fireev':435 'fireevent.click':477 'fixtur':17,126,561,613 'fixtures/mocks':31 'focus':676 'function':50,198,396,422,548 'function-bas':49,547 'general':656 'generic':647 'get':235 'getbyrol':603 'getbytext':604 'github.com':635,659,663,667,671 'github.com/cofin/flow/blob/main/templates/styleguides/frameworks/testing.md)':662 'github.com/cofin/flow/blob/main/templates/styleguides/general.md)':658 'github.com/cofin/flow/blob/main/templates/styleguides/languages/python.md)':666 'github.com/cofin/flow/blob/main/templates/styleguides/languages/typescript.md)':670 'github.com/vitest-dev/vitest/releases':634 'global':530 'group':579 'handl':367 'hello':80 'html':277,539 'http':224 'httpx':103 'id':392 'import':47,100,104,128,132,178,231,298,343,376,432,441,487,491,522 'increment':501 'index':606 'input':78,89,95,568 'instruct':690 'int':92 'integr':685 'isinst':123 'item':236,249 'jsdom':533 'json':255 'keep':673 'language/framework':648 'len':94 'length':88 'let':308 'librari':439,594 'line':541 'list':125 'litestar':227 'litestar.testing':230 'log':427 'magicmock':180,214 'manag':615 'mock':18,174,185,191,211,374,384,394,402,591 'mock_api.assert':206 'mock_api.return':193 'mockfetch':398,414,416 'mockfetch.mockresolvedvalue':410 'modifi':573 'modul':166,386,574,590 'module.external':188 'mount':488,505 'multipl':567 'myservic':216 'name':141,256,264 'new':313 'none':153 'number':318 'offici':620 'ok':196,205 'onclick':466,469,473,474,481 'parametr':75 'patch':181,187 'pitfal':619 'practic':544 'prefer':53,598 'principl':657 'promise.resolve':391 'provid':535 'py':7 'pytest':14,42,48,101,129,271,283 'pytest.fixture':134,145,164,209 'pytest.mark.anyio':106,556 'pytest.mark.parametrize':77,565 'pytest.raises':71 'python':40,46,99,127,175,228,545,665 'queri':602 'react':430 'reduc':651 'refactor':27 'refer':605,621 'references/async_testing.md':609 'rejects.tothrow':373 'relat':580 'render':433,451,454,471 'report':276,537 'reset':585 'respons':114,239,252 'response.json':124,263 'response.status':119,243,259 'result':197,203 'return':139,171,220,222 'rule':649 'run':268 'sampl':136 'scope':165 'screen':434 'screen.getbyrole':458,478 'servic':212,213,223 'service.fetch':217 'session':149,159,161 'session.rollback':163 'set':29 'setup':611 'setup/teardown':563 'share':640,644 'skill':39,655,675 'skill-testing' 'source-cofin' 'spec':215 'spec.ts':9 'specif':395,680 'spi':419,424 'sqlalchemy.ext.asyncio':131 'src':273,285 'state':586 'status':195,204 'str':90 'string':87 'structur':45,296 'styleguid':641,645 'target':569 'test':1,6,12,21,28,36,38,41,44,52,59,66,76,86,98,109,142,183,225,234,247,257,265,292,295,341,428,438,483,529,550,559,581,593,597,608,616,661 'test.ts':8 'test@example.com':144 'testclient':170,172,232,238,251 'testing-librari':437 'text':453,515,538 'threshold':282,540 'throw':327 'tobe':323,516 'tohavebeencal':482 'tohavebeencalledwith':417 'tohavelength':363 'tohavetextcont':460 'tool':679 'tool-specif':678 'topic-agent-skills' 'topic-ai-agents' 'topic-beads' 'topic-claude-code' 'topic-codex' 'topic-cursor' 'topic-developer-tools' 'topic-gemini-cli' 'topic-opencode' 'topic-plugin' 'topic-slash-commands' 'topic-spec-driven-development' 'tothrow':336 'trigger':510 'true':531 'typescript':291,297,342,375,431,486,520,575,669 'unittest.mock':177 'use':23,546,555,560,564,576,582,587,592,643 'user':137,138,140,355,358,362,600 'user-centr':599 'v8':536 'valid':687,689 'valu':194,221 'vi':347,377 'vi.clearallmocks':404 'vi.fn':390,399,470 'vi.mock':387,588 'vi.spyon':425 'vitest':16,293,305,349,383,446,496,518 'vitest.config.ts':11,521 'vitest.dev':629,632 'vitest.dev/config/coverage':631 'vitest.dev/guide/':628 'vitest/config':525 'vue':485 'vue/test-utils':490 'workflow':681 'world':83 'wrapper':504 'wrapper.find':508,513 'write':25 'yield':160 'zero':69,331,339 'zerodivisionerror':72","prices":[{"id":"cae495e9-83ee-409a-bf82-4b18c9f8a08b","listingId":"aa928796-6db7-423b-97c2-907de7bc66bc","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"cofin","category":"flow","install_from":"skills.sh"},"createdAt":"2026-04-23T13:04:02.386Z"}],"sources":[{"listingId":"aa928796-6db7-423b-97c2-907de7bc66bc","source":"github","sourceId":"cofin/flow/testing","sourceUrl":"https://github.com/cofin/flow/tree/main/skills/testing","isPrimary":false,"firstSeenAt":"2026-04-23T13:04:02.386Z","lastSeenAt":"2026-04-24T07:03:20.729Z"}],"details":{"listingId":"aa928796-6db7-423b-97c2-907de7bc66bc","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"cofin","slug":"testing","github":{"repo":"cofin/flow","stars":11,"topics":["agent-skills","ai-agents","beads","claude-code","codex","context-driven-development","cursor","developer-tools","gemini-cli","opencode","plugin","slash-commands","spec-driven-development","subagents","tdd","workflow"],"license":"apache-2.0","html_url":"https://github.com/cofin/flow","pushed_at":"2026-04-19T23:22:27Z","description":"Context-Driven Development toolkit for AI agents — spec-first planning, TDD workflow, and Beads integration.","skill_md_sha":"beb04f944b4d63fe88738148d854fba4f9df1cdc","skill_md_path":"skills/testing/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/cofin/flow/tree/main/skills/testing"},"layout":"multi","source":"github","category":"flow","frontmatter":{"name":"testing","description":"Auto-activate for test_*.py, *.test.ts, *.spec.ts, conftest.py, vitest.config.ts. Testing with pytest and vitest: fixtures, mocking, coverage, async testing, anyio. Use when: writing or refactoring tests, setting up fixtures/mocks, configuring coverage, or debugging test failures. Not for E2E/browser testing (Playwright/Cypress) or load testing."},"skills_sh_url":"https://skills.sh/cofin/flow/testing"},"updatedAt":"2026-04-24T07:03:20.729Z"}}