{"id":"aa928796-6db7-423b-97c2-907de7bc66bc","shortId":"XZgfuN","kind":"skill","title":"testing","tagline":"Use when writing or refactoring tests, editing test_*.py, *.test.ts, *.spec.ts, conftest.py, vitest.config.ts, pytest fixtures, mocks, coverage, async tests, anyio, or test failure debugging.","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-05-18T19:07:40.166Z","embedding":null,"createdAt":"2026-04-23T13:04:02.386Z","updatedAt":"2026-05-18T19:07:40.166Z","lastSeenAt":"2026-05-18T19:07:40.166Z","tsv":"'/api':376 '/api/items':105,406 '/cofin/flow/blob/main/templates/styleguides/frameworks/testing.md)':652 '/cofin/flow/blob/main/templates/styleguides/general.md)':648 '/cofin/flow/blob/main/templates/styleguides/languages/python.md)':656 '/cofin/flow/blob/main/templates/styleguides/languages/typescript.md)':660 '/config/coverage':621 '/en/stable/':612 '/en/stable/changelog.html':615 '/en/stable/testing.html':627 '/guide/':618 '/items':229,242 '/react':428 '/vitest-dev/vitest/releases':624 '0':62,70,323 '1':50,51,61,309,322,381,505 '2':52,310 '200':109,233 '201':249 '3':312,352 '5':69,72 '90':278,530,558 'add':305,676 'addit':48 'aftereach':291 'anyio':21 'anyio.readthedocs.io':626 'anyio.readthedocs.io/en/stable/testing.html':625 'anyio/pytest-anyio':598 'api':177,180,189,339,396 'app':157,161 'assert':49,81,106,110,190,230,246,250 'async':19,85,95,98,134,142,328,344,357,397,455,490,546,595,600 'asynccli':93,101 'asyncgener':139 'asyncmock':167,207 'asyncsess':121,140,144 'await':103,150,347,358,400,464,495 'base':39,45,537,542 'baselin':630 'bash':255 'basic':31,282 'beforeeach':290,299,369,391,571 'best':531 'button':436,447,460,467,497 'calc':297,300 'calc.add':308 'calc.divide':321 'calcul':295,298,302 'call':188,195,395,453 'case':671 'centric':589 'class':44,541 'class-bas':43,540 'click':443,449,463,499 'client':100,156,225,238 'client.get':104,228 'client.post':241 'code':108,232,248 'common':606 'compon':417,472,584 'configur':507 'conftest.py':13 'consol':414 'const':345,385,411,456,491 'context':602 'count':502 'counter':486,494 'cov':260,263,272,275 'cov-fail-und':274 'cov-report':262 'coverag':18,254,258,268,522,559 'creat':236 'data':206,399 'db':136 'debug':25 'def':46,53,73,96,123,135,155,170,198,221,234 'default':515 'defineconfig':511,516 'describ':287,294,332,338,366,388,430,435,480,485,565 'detail':674 'divis':55,317,325 'docs.pytest.org':611,614 'docs.pytest.org/en/stable/':610 'docs.pytest.org/en/stable/changelog.html':613 'dosometh':401 'duplic':640 'edg':670 'edit':8 'email':131 'endpoint':99 'engin':138,145 'environ':520 'error':356 'exist':409 'expect':67,79,84,289,307,320,334,349,359,368,403,432,445,468,482,500 'export':514 'fail':266,276 'failur':24 'fetch':342 'fetchinvalidendpoint':360 'fetchus':348,377 'fireev':423 'fireevent.click':465 'fixtur':16,114,549,601 'focus':664 'function':38,186,384,410,536 'function-bas':37,535 'general':644 'generic':635 'get':223 'getbyrol':591 'getbytext':592 'github.com':623,647,651,655,659 'github.com/cofin/flow/blob/main/templates/styleguides/frameworks/testing.md)':650 'github.com/cofin/flow/blob/main/templates/styleguides/general.md)':646 'github.com/cofin/flow/blob/main/templates/styleguides/languages/python.md)':654 'github.com/cofin/flow/blob/main/templates/styleguides/languages/typescript.md)':658 'github.com/vitest-dev/vitest/releases':622 'global':518 'group':567 'handl':355 'hello':68 'html':265,527 'http':212 'httpx':91 'id':380 'import':35,88,92,116,120,166,219,286,331,364,420,429,475,479,510 'increment':489 'index':594 'input':66,77,83,556 'instruct':678 'int':80 'integr':673 'isinst':111 'item':224,237 'jsdom':521 'json':243 'keep':661 'language/framework':636 'len':82 'length':76 'let':296 'librari':427,582 'line':529 'list':113 'litestar':215 'litestar.testing':218 'log':415 'magicmock':168,202 'manag':603 'mock':17,162,173,179,199,362,372,382,390,579 'mock_api.assert':194 'mock_api.return':181 'mockfetch':386,402,404 'mockfetch.mockresolvedvalue':398 'modifi':561 'modul':154,374,562,578 'module.external':176 'mount':476,493 'multipl':555 'myservic':204 'name':129,244,252 'new':301 'none':141 'number':306 'offici':608 'ok':184,193 'onclick':454,457,461,462,469 'parametr':63 'patch':169,175 'pitfal':607 'practic':532 'prefer':41,586 'principl':645 'promise.resolve':379 'provid':523 'py':10 'pytest':15,30,36,89,117,259,271 'pytest.fixture':122,133,152,197 'pytest.mark.anyio':94,544 'pytest.mark.parametrize':65,553 'pytest.raises':59 'python':28,34,87,115,163,216,533,653 'queri':590 'react':418 'reduc':639 'refactor':6 'refer':593,609 'references/async_testing.md':597 'rejects.tothrow':361 'relat':568 'render':421,439,442,459 'report':264,525 'reset':573 'respons':102,227,240 'response.json':112,251 'response.status':107,231,247 'result':185,191 'return':127,159,208,210 'rule':637 'run':256 'sampl':124 'scope':153 'screen':422 'screen.getbyrole':446,466 'servic':200,201,211 'service.fetch':205 'session':137,147,149 'session.rollback':151 'setup':599 'setup/teardown':551 'share':628,632 'skill':27,643,663 'skill-testing' 'source-cofin' 'spec':203 'spec.ts':12 'specif':383,668 'spi':407,412 'sqlalchemy.ext.asyncio':119 'src':261,273 'state':574 'status':183,192 'str':78 'string':75 'structur':33,284 'styleguid':629,633 'target':557 'test':1,7,9,20,23,26,29,32,40,47,54,64,74,86,97,130,171,213,222,235,245,253,280,283,329,416,426,471,517,538,547,569,581,585,596,604,649 'test.ts':11 'test@example.com':132 'testclient':158,160,220,226,239 'testing-librari':425 'text':441,503,526 'threshold':270,528 'throw':315 'tobe':311,504 'tohavebeencal':470 'tohavebeencalledwith':405 'tohavelength':351 'tohavetextcont':448 'tool':667 'tool-specif':666 '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':324 'trigger':498 'true':519 'typescript':279,285,330,363,419,474,508,563,657 'unittest.mock':165 'use':2,534,543,548,552,564,570,575,580,631 'user':125,126,128,343,346,350,588 'user-centr':587 'v8':524 'valid':675,677 'valu':182,209 'vi':335,365 'vi.clearallmocks':392 'vi.fn':378,387,458 'vi.mock':375,576 'vi.spyon':413 'vitest':281,293,337,371,434,484,506 'vitest.config.ts':14,509 'vitest.dev':617,620 'vitest.dev/config/coverage':619 'vitest.dev/guide/':616 'vitest/config':513 'vue':473 'vue/test-utils':478 'workflow':669 'world':71 'wrapper':492 'wrapper.find':496,501 'write':4 'yield':148 'zero':57,319,327 'zerodivisionerror':60","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-05-18T19:07:40.166Z"}],"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-27T19:07:26Z","description":"Context-Driven Development toolkit for AI agents — spec-first planning, TDD workflow, and Beads integration.","skill_md_sha":"c7f9cd2d8ff4b8d8b504c48ff93d55327e7111a3","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":"Use when writing or refactoring tests, editing test_*.py, *.test.ts, *.spec.ts, conftest.py, vitest.config.ts, pytest fixtures, mocks, coverage, async tests, anyio, or test failure debugging."},"skills_sh_url":"https://skills.sh/cofin/flow/testing"},"updatedAt":"2026-05-18T19:07:40.166Z"}}