[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6F0RHBA8S417ZP06K6S2835":3,"skill-same:01M6F0RHBA8S417ZP06K6S2835":95},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":31,"publishedAt":52,"linkOnly":53,"markdownContent":54,"markdownBody":55,"frontMatter":56,"refName":60,"commitSha":61,"skillDirectory":62,"forksCount":63,"repositoryHtmlUrl":64,"repositoryHomepageUrl":65,"ownerHtmlUrl":66,"ownerBlogUrl":6,"ownerDescription":67,"updatedAt":68,"install":69,"resources":82},"01M6F0RHBA8S417ZP06K6S2835","backend-testing",null,"Write comprehensive backend tests including unit tests, integration tests, and API tests. Use when testing REST APIs, database operations, authentication flows, or business logic. Handles Jest, Pytest, Mocha, testing strategies, mocking, and test coverage.","ComeOnOliver\u002Fskillshub","ComeOnOliver","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F43215937?v=4","01M6E5JNFECJAAVFE0W7SMWWD2","Haoran Shu","01M6E5JNNDQS7AJZDYFQ8TPZCK",66,"MIT","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Faiskillstore\u002Fmarketplace\u002Fsupercent-io\u002Fbackend-testing\u002FSKILL.md",[18,23,26],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000008","测试与质量","SCENARIO","使用场景",{"categoryNo":24,"categoryName":25,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000010","后端与接口",{"categoryNo":27,"categoryName":28,"taxonomyType":29,"taxonomyTypeDesc":30},"01JBSEEDCAT000000000000030","开发工程师","PROFESSION","适用职业",[32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51],"testing","backend testing","backend","flows","unit-test","unit","database operations","strategies","jest","pytest","test coverage","coverage","write","api","database","chatgpt","authentication","backend tests","integration tests","gemini","2026-11-10 10:12:30",false,"---\nname: backend-testing\ndescription: Write comprehensive backend tests including unit tests, integration tests, and API tests. Use when testing REST APIs, database operations, authentication flows, or business logic. Handles Jest, Pytest, Mocha, testing strategies, mocking, and test coverage.\nmetadata:\n  tags: testing, backend, unit-test, integration-test, API-test, Jest, Pytest, TDD\n  platforms: Claude, ChatGPT, Gemini\n---\n\n\n# Backend Testing\n\n\n## When to use this skill\n\nSpecific situations that should trigger this skill:\n\n- **New feature development**: Write tests first using TDD (Test-Driven Development)\n- **Adding API endpoints**: Test success and failure cases for REST APIs\n- **Bug fixes**: Add tests to prevent regressions\n- **Before refactoring**: Write tests that guarantee existing behavior\n- **CI\u002FCD setup**: Build automated test pipelines\n\n## Input Format\n\nFormat and required\u002Foptional information to collect from the user:\n\n### Required information\n- **Framework**: Express, Django, FastAPI, Spring Boot, etc.\n- **Test tool**: Jest, Pytest, Mocha\u002FChai, JUnit, etc.\n- **Test target**: API endpoints, business logic, DB operations, etc.\n\n### Optional information\n- **Database**: PostgreSQL, MySQL, MongoDB (default: in-memory DB)\n- **Mocking library**: jest.mock, sinon, unittest.mock (default: framework built-in)\n- **Coverage target**: 80%, 90%, etc. (default: 80%)\n- **E2E tool**: Supertest, TestClient, RestAssured (optional)\n\n### Input example\n\n```\nTest the user authentication endpoints for an Express.js API:\n- Framework: Express + TypeScript\n- Test tool: Jest + Supertest\n- Target: POST \u002Fauth\u002Fregister, POST \u002Fauth\u002Flogin\n- DB: PostgreSQL (in-memory for tests)\n- Coverage: 90% or above\n```\n\n## Instructions\n\nStep-by-step task order to follow precisely.\n\n### Step 1: Set up the test environment\n\nInstall and configure the test framework and tools.\n\n**Tasks**:\n- Install test libraries\n- Configure test database (in-memory or separate DB)\n- Separate environment variables (.env.test)\n- Configure jest.config.js or pytest.ini\n\n**Example** (Node.js + Jest + Supertest):\n```bash\nnpm install --save-dev jest ts-jest @types\u002Fjest supertest @types\u002Fsupertest\n```\n\n**jest.config.js**:\n```javascript\nmodule.exports = {\n  preset: 'ts-jest',\n  testEnvironment: 'node',\n  roots: ['\u003CrootDir>\u002Fsrc'],\n  testMatch: ['**\u002F__tests__\u002F**\u002F*.test.ts'],\n  collectCoverageFrom: [\n    'src\u002F**\u002F*.ts',\n    '!src\u002F**\u002F*.d.ts',\n    '!src\u002F__tests__\u002F**'\n  ],\n  coverageThreshold: {\n    global: {\n      branches: 80,\n      functions: 80,\n      lines: 80,\n      statements: 80\n    }\n  },\n  setupFilesAfterEnv: ['\u003CrootDir>\u002Fsrc\u002F__tests__\u002Fsetup.ts']\n};\n```\n\n**setup.ts** (global test configuration):\n```typescript\nimport { db } from '..\u002Fdatabase';\n\n\u002F\u002F Reset DB before each test\nbeforeEach(async () => {\n  await db.migrate.latest();\n  await db.seed.run();\n});\n\n\u002F\u002F Clean up after each test\nafterEach(async () => {\n  await db.migrate.rollback();\n});\n\n\u002F\u002F Close connection after all tests complete\nafterAll(async () => {\n  await db.destroy();\n});\n```\n\n### Step 2: Write Unit Tests (business logic)\n\nWrite unit tests for individual functions and classes.\n\n**Tasks**:\n- Test pure functions (no dependencies)\n- Isolate dependencies via mocking\n- Test edge cases (boundary values, exceptions)\n- AAA pattern (Arrange-Act-Assert)\n\n**Decision criteria**:\n- No external dependencies (DB, API) -> pure Unit Test\n- External dependencies present -> use Mock\u002FStub\n- Complex logic -> test various input cases\n\n**Example** (password validation function):\n```typescript\n\u002F\u002F src\u002Futils\u002Fpassword.ts\nexport function validatePassword(password: string): { valid: boolean; errors: string[] } {\n  const errors: string[] = [];\n\n  if (password.length \u003C 8) {\n    errors.push('Password must be at least 8 characters');\n  }\n\n  if (!\u002F[A-Z]\u002F.test(password)) {\n    errors.push('Password must contain uppercase letter');\n  }\n\n  if (!\u002F[a-z]\u002F.test(password)) {\n    errors.push('Password must contain lowercase letter');\n  }\n\n  if (!\u002F\\d\u002F.test(password)) {\n    errors.push('Password must contain number');\n  }\n\n  if (!\u002F[!@#$%^&*]\u002F.test(password)) {\n    errors.push('Password must contain special character');\n  }\n\n  return { valid: errors.length === 0, errors };\n}\n\n\u002F\u002F src\u002F__tests__\u002Futils\u002Fpassword.test.ts\nimport { validatePassword } from '..\u002F..\u002Futils\u002Fpassword';\n\ndescribe('validatePassword', () => {\n  it('should accept valid password', () => {\n    const result = validatePassword('Password123!');\n    expect(result.valid).toBe(true);\n    expect(result.errors).toHaveLength(0);\n  });\n\n  it('should reject password shorter than 8 characters', () => {\n    const result = validatePassword('Pass1!');\n    expect(result.valid).toBe(false);\n    expect(result.errors).toContain('Password must be at least 8 characters');\n  });\n\n  it('should reject password without uppercase', () => {\n    const result = validatePassword('password123!');\n    expect(result.valid).toBe(false);\n    expect(result.errors).toContain('Password must contain uppercase letter');\n  });\n\n  it('should reject password without lowercase', () => {\n    const result = validatePassword('PASSWORD123!');\n    expect(result.valid).toBe(false);\n    expect(result.errors).toContain('Password must contain lowercase letter');\n  });\n\n  it('should reject password without number', () => {\n    const result = validatePassword('Password!');\n    expect(result.valid).toBe(false);\n    expect(result.errors).toContain('Password must contain number');\n  });\n\n  it('should reject password without special character', () => {\n    const result = validatePassword('Password123');\n    expect(result.valid).toBe(false);\n    expect(result.errors).toContain('Password must contain special character');\n  });\n\n  it('should return multiple errors for invalid password', () => {\n    const result = validatePassword('pass');\n    expect(result.valid).toBe(false);\n    expect(result.errors.length).toBeGreaterThan(1);\n  });\n});\n```\n\n### Step 3: Integration Test (API endpoints)\n\nWrite integration tests for API endpoints.\n\n**Tasks**:\n- Test HTTP requests\u002Fresponses\n- Success cases (200, 201)\n- Failure cases (400, 401, 404, 500)\n- Authentication\u002Fauthorization tests\n- Input validation tests\n\n**Checklist**:\n- [x] Verify status code\n- [x] Validate response body structure\n- [x] Confirm database state changes\n- [x] Validate error messages\n\n**Example** (Express.js + Supertest):\n```typescript\n\u002F\u002F src\u002F__tests__\u002Fapi\u002Fauth.test.ts\nimport request from 'supertest';\nimport app from '..\u002F..\u002Fapp';\nimport { db } from '..\u002F..\u002Fdatabase';\n\ndescribe('POST \u002Fauth\u002Fregister', () => {\n  it('should register new user successfully', async () => {\n    const response = await request(app)\n      .post('\u002Fapi\u002Fauth\u002Fregister')\n      .send({\n        email: 'test@example.com',\n        username: 'testuser',\n        password: 'Password123!'\n      });\n\n    expect(response.status).toBe(201);\n    expect(response.body).toHaveProperty('user');\n    expect(response.body).toHaveProperty('accessToken');\n    expect(response.body.user.email).toBe('test@example.com');\n\n    \u002F\u002F Verify the record was actually saved to DB\n    const user = await db.user.findUnique({ where: { email: 'test@example.com' } });\n    expect(user).toBeTruthy();\n    expect(user.username).toBe('testuser');\n  });\n\n  it('should reject duplicate email', async () => {\n    \u002F\u002F Create first user\n    await request(app)\n      .post('\u002Fapi\u002Fauth\u002Fregister')\n      .send({\n        email: 'test@example.com',\n        username: 'user1',\n        password: 'Password123!'\n      });\n\n    \u002F\u002F Second attempt with same email\n    const response = await request(app)\n      .post('\u002Fapi\u002Fauth\u002Fregister')\n      .send({\n        email: 'test@example.com',\n        username: 'user2',\n        password: 'Password123!'\n      });\n\n    expect(response.status).toBe(409);\n    expect(response.body.error).toContain('already exists');\n  });\n\n  it('should reject weak password', async () => {\n    const response = await request(app)\n      .post('\u002Fapi\u002Fauth\u002Fregister')\n      .send({\n        email: 'test@example.com',\n        username: 'testuser',\n        password: 'weak'\n      });\n\n    expect(response.status).toBe(400);\n    expect(response.body.error).toBeDefined();\n  });\n\n  it('should reject missing fields', async () => {\n    const response = await request(app)\n      .post('\u002Fapi\u002Fauth\u002Fregister')\n      .send({\n        email: 'test@example.com'\n        \u002F\u002F username, password omitted\n      });\n\n    expect(response.status).toBe(400);\n  });\n});\n\ndescribe('POST \u002Fauth\u002Flogin', () => {\n  beforeEach(async () => {\n    \u002F\u002F Create test user\n    await request(app)\n      .post('\u002Fapi\u002Fauth\u002Fregister')\n      .send({\n        email: 'test@example.com',\n        username: 'testuser',\n        password: 'Password123!'\n      });\n  });\n\n  it('should login with valid credentials', async () => {\n    const response = await request(app)\n      .post('\u002Fapi\u002Fauth\u002Flogin')\n      .send({\n        email: 'test@example.com',\n        password: 'Password123!'\n      });\n\n    expect(response.status).toBe(200);\n    expect(response.body).toHaveProperty('accessToken');\n    expect(response.body).toHaveProperty('refreshToken');\n    expect(response.body.user.email).toBe('test@example.com');\n  });\n\n  it('should reject invalid password', async () => {\n    const response = await request(app)\n      .post('\u002Fapi\u002Fauth\u002Flogin')\n      .send({\n        email: 'test@example.com',\n        password: 'WrongPassword123!'\n      });\n\n    expect(response.status).toBe(401);\n    expect(response.body.error).toContain('Invalid credentials');\n  });\n\n  it('should reject non-existent user', async () => {\n    const response = await request(app)\n      .post('\u002Fapi\u002Fauth\u002Flogin')\n      .send({\n        email: 'nonexistent@example.com',\n        password: 'Password123!'\n      });\n\n    expect(response.status).toBe(401);\n  });\n});\n```\n\n### Step 4: Authentication\u002FAuthorization Tests\n\nTest JWT tokens and role-based access control.\n\n**Tasks**:\n- Confirm 401 when accessing without a token\n- Confirm successful access with a valid token\n- Test expired token handling\n- Role-based permission tests\n\n**Example**:\n```typescript\ndescribe('Protected Routes', () => {\n  let accessToken: string;\n  let adminToken: string;\n\n  beforeEach(async () => {\n    \u002F\u002F Regular user token\n    const userResponse = await request(app)\n      .post('\u002Fapi\u002Fauth\u002Fregister')\n      .send({\n        email: 'user@example.com',\n        username: 'user',\n        password: 'Password123!'\n      });\n    accessToken = userResponse.body.accessToken;\n\n    \u002F\u002F Admin token\n    const adminResponse = await request(app)\n      .post('\u002Fapi\u002Fauth\u002Fregister')\n      .send({\n        email: 'admin@example.com',\n        username: 'admin',\n        password: 'Password123!'\n      });\n    \u002F\u002F Update role to 'admin' in DB\n    await db.user.update({\n      where: { email: 'admin@example.com' },\n      data: { role: 'admin' }\n    });\n    \u002F\u002F Log in again to get a new token\n    const loginResponse = await request(app)\n      .post('\u002Fapi\u002Fauth\u002Flogin')\n      .send({\n        email: 'admin@example.com',\n        password: 'Password123!'\n      });\n    adminToken = loginResponse.body.accessToken;\n  });\n\n  describe('GET \u002Fapi\u002Fauth\u002Fme', () => {\n    it('should return current user with valid token', async () => {\n      const response = await request(app)\n        .get('\u002Fapi\u002Fauth\u002Fme')\n        .set('Authorization', `Bearer ${accessToken}`);\n\n      expect(response.status).toBe(200);\n      expect(response.body.user.email).toBe('user@example.com');\n    });\n\n    it('should reject request without token', async () => {\n      const response = await request(app)\n        .get('\u002Fapi\u002Fauth\u002Fme');\n\n      expect(response.status).toBe(401);\n    });\n\n    it('should reject request with invalid token', async () => {\n      const response = await request(app)\n        .get('\u002Fapi\u002Fauth\u002Fme')\n        .set('Authorization', 'Bearer invalid-token');\n\n      expect(response.status).toBe(403);\n    });\n  });\n\n  describe('DELETE \u002Fapi\u002Fusers\u002F:id (Admin only)', () => {\n    it('should allow admin to delete user', async () => {\n      const targetUser = await db.user.findUnique({ where: { email: 'user@example.com' } });\n\n      const response = await request(app)\n        .delete(`\u002Fapi\u002Fusers\u002F${targetUser.id}`)\n        .set('Authorization', `Bearer ${adminToken}`);\n\n      expect(response.status).toBe(200);\n    });\n\n    it('should forbid non-admin from deleting user', async () => {\n      const targetUser = await db.user.findUnique({ where: { email: 'user@example.com' } });\n\n      const response = await request(app)\n        .delete(`\u002Fapi\u002Fusers\u002F${targetUser.id}`)\n        .set('Authorization', `Bearer ${accessToken}`);\n\n      expect(response.status).toBe(403);\n    });\n  });\n});\n```\n\n### Step 5: Mocking and Test Isolation\n\nMock external dependencies to isolate tests.\n\n**Tasks**:\n- Mock external APIs\n- Mock email sending\n- Mock file system\n- Mock time-related functions\n\n**Example** (mocking an external API):\n```typescript\n\u002F\u002F src\u002Fservices\u002FemailService.ts\nexport async function sendVerificationEmail(email: string, token: string): Promise\u003Cvoid> {\n  const response = await fetch('https:\u002F\u002Fapi.sendgrid.com\u002Fv3\u002Fmail\u002Fsend', {\n    method: 'POST',\n    headers: { 'Authorization': `Bearer ${process.env.SENDGRID_API_KEY}` },\n    body: JSON.stringify({\n      to: email,\n      subject: 'Verify your email',\n      html: `\u003Ca href=\"https:\u002F\u002Fexample.com\u002Fverify?token=${token}\">Verify\u003C\u002Fa>`\n    })\n  });\n\n  if (!response.ok) {\n    throw new Error('Failed to send email');\n  }\n}\n\n\u002F\u002F src\u002F__tests__\u002Fservices\u002FemailService.test.ts\nimport { sendVerificationEmail } from '..\u002F..\u002Fservices\u002FemailService';\n\n\u002F\u002F Mock fetch\nglobal.fetch = jest.fn();\n\ndescribe('sendVerificationEmail', () => {\n  beforeEach(() => {\n    (fetch as jest.Mock).mockClear();\n  });\n\n  it('should send email successfully', async () => {\n    (fetch as jest.Mock).mockResolvedValueOnce({\n      ok: true,\n      status: 200\n    });\n\n    await expect(sendVerificationEmail('test@example.com', 'token123'))\n      .resolves\n      .toBeUndefined();\n\n    expect(fetch).toHaveBeenCalledWith(\n      'https:\u002F\u002Fapi.sendgrid.com\u002Fv3\u002Fmail\u002Fsend',\n      expect.objectContaining({\n        method: 'POST'\n      })\n    );\n  });\n\n  it('should throw error if email sending fails', async () => {\n    (fetch as jest.Mock).mockResolvedValueOnce({\n      ok: false,\n      status: 500\n    });\n\n    await expect(sendVerificationEmail('test@example.com', 'token123'))\n      .rejects\n      .toThrow('Failed to send email');\n  });\n});\n```\n\n## Output format\n\nDefines the exact format that outputs must follow.\n\n### Basic structure\n\n```\nproject\u002F\n├── src\u002F\n│   ├── __tests__\u002F\n│   │   ├── setup.ts                 # Global test configuration\n│   │   ├── utils\u002F\n│   │   │   └── password.test.ts     # Unit tests\n│   │   ├── services\u002F\n│   │   │   └── emailService.test.ts\n│   │   └── api\u002F\n│   │       ├── auth.test.ts         # Integration tests\n│   │       └── users.test.ts\n│   └── ...\n├── jest.config.js\n└── package.json\n```\n\n### Test run scripts (package.json)\n\n```json\n{\n  \"scripts\": {\n    \"test\": \"jest\",\n    \"test:watch\": \"jest --watch\",\n    \"test:coverage\": \"jest --coverage\",\n    \"test:ci\": \"jest --ci --coverage --maxWorkers=2\"\n  }\n}\n```\n\n### Coverage report\n\n```bash\n$ npm run test:coverage\n\n--------------------------|---------|----------|---------|---------|\nFile                      | % Stmts | % Branch | % Funcs | % Lines |\n--------------------------|---------|----------|---------|---------|\nAll files                 |   92.5  |   88.3   |   95.2  |   92.8  |\n auth\u002F                    |   95.0  |   90.0   |  100.0  |   95.0  |\n  middleware.ts           |   95.0  |   90.0   |  100.0  |   95.0  |\n  routes.ts               |   95.0  |   90.0   |  100.0  |   95.0  |\n utils\u002F                   |   90.0  |   85.0   |   90.0  |   90.0  |\n  password.ts             |   90.0  |   85.0   |   90.0  |   90.0  |\n--------------------------|---------|----------|---------|---------|\n```\n\n## Constraints\n\nRules and prohibitions that must be strictly followed.\n\n### Required rules (MUST)\n\n1. **Test isolation**: Each test must be runnable independently\n   - Reset state with beforeEach\u002FafterEach\n   - Do not depend on test execution order\n\n2. **Clear test names**: The name must convey what the test verifies\n   - ✅ 'should reject duplicate email'\n   - ❌ 'test1'\n\n3. **AAA pattern**: Arrange (setup) - Act (execute) - Assert (verify) structure\n   - Improves readability\n   - Clarifies test intent\n\n### Prohibited (MUST NOT)\n\n1. **No production DB**: Tests must use a separate or in-memory DB\n   - Risk of losing real data\n   - Cannot isolate tests\n\n2. **No real external API calls**: Mock all external services\n   - Removes network dependency\n   - Speeds up tests\n   - Reduces costs\n\n3. **No Sleep\u002FTimeout abuse**: Use fake timers for time-based tests\n   - jest.useFakeTimers()\n   - Prevents test slowdowns\n\n### Security rules\n\n- **No hardcoded secrets**: Never hardcode API keys or passwords in test code\n- **Separate environment variables**: Use .env.test file\n\n## Examples\n\n### Example 1: Python FastAPI tests (Pytest)\n\n**Situation**: Testing a FastAPI REST API\n\n**User request**:\n```\nTest the user API built with FastAPI using pytest.\n```\n\n**Final result**:\n```python\n# tests\u002Fconftest.py\nimport pytest\nfrom fastapi.testclient import TestClient\nfrom sqlalchemy import create_engine\nfrom sqlalchemy.orm import sessionmaker\n\nfrom app.main import app\nfrom app.database import Base, get_db\n\n# In-memory SQLite for tests\nSQLALCHEMY_DATABASE_URL = \"sqlite:\u002F\u002F\u002F.\u002Ftest.db\"\nengine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={\"check_same_thread\": False})\nTestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)\n\n@pytest.fixture(scope=\"function\")\ndef db_session():\n    Base.metadata.create_all(bind=engine)\n    db = TestingSessionLocal()\n    try:\n        yield db\n    finally:\n        db.close()\n        Base.metadata.drop_all(bind=engine)\n\n@pytest.fixture(scope=\"function\")\ndef client(db_session):\n    def override_get_db():\n        try:\n            yield db_session\n        finally:\n            db_session.close()\n\n    app.dependency_overrides[get_db] = override_get_db\n    yield TestClient(app)\n    app.dependency_overrides.clear()\n\n# tests\u002Ftest_auth.py\ndef test_register_user_success(client):\n    response = client.post(\"\u002Fauth\u002Fregister\", json={\n        \"email\": \"test@example.com\",\n        \"username\": \"testuser\",\n        \"password\": \"Password123!\"\n    })\n\n    assert response.status_code == 201\n    assert \"access_token\" in response.json()\n    assert response.json()[\"user\"][\"email\"] == \"test@example.com\"\n\ndef test_register_duplicate_email(client):\n    # First user\n    client.post(\"\u002Fauth\u002Fregister\", json={\n        \"email\": \"test@example.com\",\n        \"username\": \"user1\",\n        \"password\": \"Password123!\"\n    })\n\n    # Duplicate email\n    response = client.post(\"\u002Fauth\u002Fregister\", json={\n        \"email\": \"test@example.com\",\n        \"username\": \"user2\",\n        \"password\": \"Password123!\"\n    })\n\n    assert response.status_code == 409\n    assert \"already exists\" in response.json()[\"detail\"]\n\ndef test_login_success(client):\n    # Register\n    client.post(\"\u002Fauth\u002Fregister\", json={\n        \"email\": \"test@example.com\",\n        \"username\": \"testuser\",\n        \"password\": \"Password123!\"\n    })\n\n    # Login\n    response = client.post(\"\u002Fauth\u002Flogin\", json={\n        \"email\": \"test@example.com\",\n        \"password\": \"Password123!\"\n    })\n\n    assert response.status_code == 200\n    assert \"access_token\" in response.json()\n\ndef test_protected_route_without_token(client):\n    response = client.get(\"\u002Fauth\u002Fme\")\n    assert response.status_code == 401\n\ndef test_protected_route_with_token(client):\n    # Register and get token\n    register_response = client.post(\"\u002Fauth\u002Fregister\", json={\n        \"email\": \"test@example.com\",\n        \"username\": \"testuser\",\n        \"password\": \"Password123!\"\n    })\n    token = register_response.json()[\"access_token\"]\n\n    # Access protected route\n    response = client.get(\"\u002Fauth\u002Fme\", headers={\n        \"Authorization\": f\"Bearer {token}\"\n    })\n\n    assert response.status_code == 200\n    assert response.json()[\"email\"] == \"test@example.com\"\n```\n\n## Best practices\n\n### Quality improvements\n\n1. **TDD (Test-Driven Development)**: Write tests before writing code\n   - Clarifies requirements\n   - Improves design\n   - Naturally achieves high coverage\n\n2. **Given-When-Then pattern**: Write tests in BDD style\n   ```typescript\n   it('should return 404 when user not found', async () => {\n     \u002F\u002F Given: a non-existent user ID\n     const nonExistentId = 'non-existent-uuid';\n\n     \u002F\u002F When: attempting to look up that user\n     const response = await request(app).get(`\u002Fusers\u002F${nonExistentId}`);\n\n     \u002F\u002F Then: 404 response\n     expect(response.status).toBe(404);\n   });\n   ```\n\n3. **Test Fixtures**: Reusable test data\n   ```typescript\n   const validUser = {\n     email: 'test@example.com',\n     username: 'testuser',\n     password: 'Password123!'\n   };\n   ```\n\n### Efficiency improvements\n\n- **Parallel execution**: Speed up tests with Jest's `--maxWorkers` option\n- **Snapshot Testing**: Save snapshots of UI components or JSON responses\n- **Coverage thresholds**: Enforce minimum coverage in jest.config.js\n\n## Common Issues\n\n### Issue 1: Test failures caused by shared state between tests\n\n**Symptom**: Passes individually but fails when run together\n\n**Cause**: DB state shared due to missing beforeEach\u002FafterEach\n\n**Fix**:\n```typescript\nbeforeEach(async () => {\n  await db.migrate.rollback();\n  await db.migrate.latest();\n});\n```\n\n### Issue 2: \"Jest did not exit one second after the test run\"\n\n**Symptom**: Process does not exit after tests complete\n\n**Cause**: DB connections, servers, etc. not cleaned up\n\n**Fix**:\n```typescript\nafterAll(async () => {\n  await db.destroy();\n  await server.close();\n});\n```\n\n### Issue 3: Async test timeout\n\n**Symptom**: \"Timeout - Async callback was not invoked\"\n\n**Cause**: Missing async\u002Fawait or unhandled Promise\n\n**Fix**:\n```typescript\n\u002F\u002F Bad\nit('should work', () => {\n  request(app).get('\u002Fusers');  \u002F\u002F Promise not handled\n});\n\n\u002F\u002F Good\nit('should work', async () => {\n  await request(app).get('\u002Fusers');\n});\n```\n\n## References\n\n### Official docs\n- [Jest Documentation](https:\u002F\u002Fjestjs.io\u002Fdocs\u002Fgetting-started)\n- [Pytest Documentation](https:\u002F\u002Fdocs.pytest.org\u002F)\n- [Supertest GitHub](https:\u002F\u002Fgithub.com\u002Fvisionmedia\u002Fsupertest)\n\n### Learning resources\n- [Testing JavaScript with Kent C. Dodds](https:\u002F\u002Ftestingjavascript.com\u002F)\n- [Test-Driven Development by Example (Kent Beck)](https:\u002F\u002Fwww.amazon.com\u002FTest-Driven-Development-Kent-Beck\u002Fdp\u002F0321146530)\n\n### Tools\n- [Istanbul\u002Fnyc](https:\u002F\u002Fistanbul.js.org\u002F) - code coverage\n- [nock](https:\u002F\u002Fgithub.com\u002Fnock\u002Fnock) - HTTP mocking\n- [faker.js](https:\u002F\u002Ffakerjs.dev\u002F) - test data generation\n\n## Metadata\n\n### Version\n- **Current version**: 1.0.0\n- **Last updated**: 2025-01-01\n- **Compatible platforms**: Claude, ChatGPT, Gemini\n\n### Related skills\n- [api-design](..\u002Fapi-design\u002FSKILL.md): Design APIs alongside tests\n- [authentication-setup](..\u002Fauthentication\u002FSKILL.md): Test authentication systems\n\n### Tags\n`#testing` `#backend` `#Jest` `#Pytest` `#unit-test` `#integration-test` `#TDD` `#API-test`\n","# Backend Testing\n\n\n## When to use this skill\n\nSpecific situations that should trigger this skill:\n\n- **New feature development**: Write tests first using TDD (Test-Driven Development)\n- **Adding API endpoints**: Test success and failure cases for REST APIs\n- **Bug fixes**: Add tests to prevent regressions\n- **Before refactoring**: Write tests that guarantee existing behavior\n- **CI\u002FCD setup**: Build automated test pipelines\n\n## Input Format\n\nFormat and required\u002Foptional information to collect from the user:\n\n### Required information\n- **Framework**: Express, Django, FastAPI, Spring Boot, etc.\n- **Test tool**: Jest, Pytest, Mocha\u002FChai, JUnit, etc.\n- **Test target**: API endpoints, business logic, DB operations, etc.\n\n### Optional information\n- **Database**: PostgreSQL, MySQL, MongoDB (default: in-memory DB)\n- **Mocking library**: jest.mock, sinon, unittest.mock (default: framework built-in)\n- **Coverage target**: 80%, 90%, etc. (default: 80%)\n- **E2E tool**: Supertest, TestClient, RestAssured (optional)\n\n### Input example\n\n```\nTest the user authentication endpoints for an Express.js API:\n- Framework: Express + TypeScript\n- Test tool: Jest + Supertest\n- Target: POST \u002Fauth\u002Fregister, POST \u002Fauth\u002Flogin\n- DB: PostgreSQL (in-memory for tests)\n- Coverage: 90% or above\n```\n\n## Instructions\n\nStep-by-step task order to follow precisely.\n\n### Step 1: Set up the test environment\n\nInstall and configure the test framework and tools.\n\n**Tasks**:\n- Install test libraries\n- Configure test database (in-memory or separate DB)\n- Separate environment variables (.env.test)\n- Configure jest.config.js or pytest.ini\n\n**Example** (Node.js + Jest + Supertest):\n```bash\nnpm install --save-dev jest ts-jest @types\u002Fjest supertest @types\u002Fsupertest\n```\n\n**jest.config.js**:\n```javascript\nmodule.exports = {\n  preset: 'ts-jest',\n  testEnvironment: 'node',\n  roots: ['\u003CrootDir>\u002Fsrc'],\n  testMatch: ['**\u002F__tests__\u002F**\u002F*.test.ts'],\n  collectCoverageFrom: [\n    'src\u002F**\u002F*.ts',\n    '!src\u002F**\u002F*.d.ts',\n    '!src\u002F__tests__\u002F**'\n  ],\n  coverageThreshold: {\n    global: {\n      branches: 80,\n      functions: 80,\n      lines: 80,\n      statements: 80\n    }\n  },\n  setupFilesAfterEnv: ['\u003CrootDir>\u002Fsrc\u002F__tests__\u002Fsetup.ts']\n};\n```\n\n**setup.ts** (global test configuration):\n```typescript\nimport { db } from '..\u002Fdatabase';\n\n\u002F\u002F Reset DB before each test\nbeforeEach(async () => {\n  await db.migrate.latest();\n  await db.seed.run();\n});\n\n\u002F\u002F Clean up after each test\nafterEach(async () => {\n  await db.migrate.rollback();\n});\n\n\u002F\u002F Close connection after all tests complete\nafterAll(async () => {\n  await db.destroy();\n});\n```\n\n### Step 2: Write Unit Tests (business logic)\n\nWrite unit tests for individual functions and classes.\n\n**Tasks**:\n- Test pure functions (no dependencies)\n- Isolate dependencies via mocking\n- Test edge cases (boundary values, exceptions)\n- AAA pattern (Arrange-Act-Assert)\n\n**Decision criteria**:\n- No external dependencies (DB, API) -> pure Unit Test\n- External dependencies present -> use Mock\u002FStub\n- Complex logic -> test various input cases\n\n**Example** (password validation function):\n```typescript\n\u002F\u002F src\u002Futils\u002Fpassword.ts\nexport function validatePassword(password: string): { valid: boolean; errors: string[] } {\n  const errors: string[] = [];\n\n  if (password.length \u003C 8) {\n    errors.push('Password must be at least 8 characters');\n  }\n\n  if (!\u002F[A-Z]\u002F.test(password)) {\n    errors.push('Password must contain uppercase letter');\n  }\n\n  if (!\u002F[a-z]\u002F.test(password)) {\n    errors.push('Password must contain lowercase letter');\n  }\n\n  if (!\u002F\\d\u002F.test(password)) {\n    errors.push('Password must contain number');\n  }\n\n  if (!\u002F[!@#$%^&*]\u002F.test(password)) {\n    errors.push('Password must contain special character');\n  }\n\n  return { valid: errors.length === 0, errors };\n}\n\n\u002F\u002F src\u002F__tests__\u002Futils\u002Fpassword.test.ts\nimport { validatePassword } from '..\u002F..\u002Futils\u002Fpassword';\n\ndescribe('validatePassword', () => {\n  it('should accept valid password', () => {\n    const result = validatePassword('Password123!');\n    expect(result.valid).toBe(true);\n    expect(result.errors).toHaveLength(0);\n  });\n\n  it('should reject password shorter than 8 characters', () => {\n    const result = validatePassword('Pass1!');\n    expect(result.valid).toBe(false);\n    expect(result.errors).toContain('Password must be at least 8 characters');\n  });\n\n  it('should reject password without uppercase', () => {\n    const result = validatePassword('password123!');\n    expect(result.valid).toBe(false);\n    expect(result.errors).toContain('Password must contain uppercase letter');\n  });\n\n  it('should reject password without lowercase', () => {\n    const result = validatePassword('PASSWORD123!');\n    expect(result.valid).toBe(false);\n    expect(result.errors).toContain('Password must contain lowercase letter');\n  });\n\n  it('should reject password without number', () => {\n    const result = validatePassword('Password!');\n    expect(result.valid).toBe(false);\n    expect(result.errors).toContain('Password must contain number');\n  });\n\n  it('should reject password without special character', () => {\n    const result = validatePassword('Password123');\n    expect(result.valid).toBe(false);\n    expect(result.errors).toContain('Password must contain special character');\n  });\n\n  it('should return multiple errors for invalid password', () => {\n    const result = validatePassword('pass');\n    expect(result.valid).toBe(false);\n    expect(result.errors.length).toBeGreaterThan(1);\n  });\n});\n```\n\n### Step 3: Integration Test (API endpoints)\n\nWrite integration tests for API endpoints.\n\n**Tasks**:\n- Test HTTP requests\u002Fresponses\n- Success cases (200, 201)\n- Failure cases (400, 401, 404, 500)\n- Authentication\u002Fauthorization tests\n- Input validation tests\n\n**Checklist**:\n- [x] Verify status code\n- [x] Validate response body structure\n- [x] Confirm database state changes\n- [x] Validate error messages\n\n**Example** (Express.js + Supertest):\n```typescript\n\u002F\u002F src\u002F__tests__\u002Fapi\u002Fauth.test.ts\nimport request from 'supertest';\nimport app from '..\u002F..\u002Fapp';\nimport { db } from '..\u002F..\u002Fdatabase';\n\ndescribe('POST \u002Fauth\u002Fregister', () => {\n  it('should register new user successfully', async () => {\n    const response = await request(app)\n      .post('\u002Fapi\u002Fauth\u002Fregister')\n      .send({\n        email: 'test@example.com',\n        username: 'testuser',\n        password: 'Password123!'\n      });\n\n    expect(response.status).toBe(201);\n    expect(response.body).toHaveProperty('user');\n    expect(response.body).toHaveProperty('accessToken');\n    expect(response.body.user.email).toBe('test@example.com');\n\n    \u002F\u002F Verify the record was actually saved to DB\n    const user = await db.user.findUnique({ where: { email: 'test@example.com' } });\n    expect(user).toBeTruthy();\n    expect(user.username).toBe('testuser');\n  });\n\n  it('should reject duplicate email', async () => {\n    \u002F\u002F Create first user\n    await request(app)\n      .post('\u002Fapi\u002Fauth\u002Fregister')\n      .send({\n        email: 'test@example.com',\n        username: 'user1',\n        password: 'Password123!'\n      });\n\n    \u002F\u002F Second attempt with same email\n    const response = await request(app)\n      .post('\u002Fapi\u002Fauth\u002Fregister')\n      .send({\n        email: 'test@example.com',\n        username: 'user2',\n        password: 'Password123!'\n      });\n\n    expect(response.status).toBe(409);\n    expect(response.body.error).toContain('already exists');\n  });\n\n  it('should reject weak password', async () => {\n    const response = await request(app)\n      .post('\u002Fapi\u002Fauth\u002Fregister')\n      .send({\n        email: 'test@example.com',\n        username: 'testuser',\n        password: 'weak'\n      });\n\n    expect(response.status).toBe(400);\n    expect(response.body.error).toBeDefined();\n  });\n\n  it('should reject missing fields', async () => {\n    const response = await request(app)\n      .post('\u002Fapi\u002Fauth\u002Fregister')\n      .send({\n        email: 'test@example.com'\n        \u002F\u002F username, password omitted\n      });\n\n    expect(response.status).toBe(400);\n  });\n});\n\ndescribe('POST \u002Fauth\u002Flogin', () => {\n  beforeEach(async () => {\n    \u002F\u002F Create test user\n    await request(app)\n      .post('\u002Fapi\u002Fauth\u002Fregister')\n      .send({\n        email: 'test@example.com',\n        username: 'testuser',\n        password: 'Password123!'\n      });\n  });\n\n  it('should login with valid credentials', async () => {\n    const response = await request(app)\n      .post('\u002Fapi\u002Fauth\u002Flogin')\n      .send({\n        email: 'test@example.com',\n        password: 'Password123!'\n      });\n\n    expect(response.status).toBe(200);\n    expect(response.body).toHaveProperty('accessToken');\n    expect(response.body).toHaveProperty('refreshToken');\n    expect(response.body.user.email).toBe('test@example.com');\n  });\n\n  it('should reject invalid password', async () => {\n    const response = await request(app)\n      .post('\u002Fapi\u002Fauth\u002Flogin')\n      .send({\n        email: 'test@example.com',\n        password: 'WrongPassword123!'\n      });\n\n    expect(response.status).toBe(401);\n    expect(response.body.error).toContain('Invalid credentials');\n  });\n\n  it('should reject non-existent user', async () => {\n    const response = await request(app)\n      .post('\u002Fapi\u002Fauth\u002Flogin')\n      .send({\n        email: 'nonexistent@example.com',\n        password: 'Password123!'\n      });\n\n    expect(response.status).toBe(401);\n  });\n});\n```\n\n### Step 4: Authentication\u002FAuthorization Tests\n\nTest JWT tokens and role-based access control.\n\n**Tasks**:\n- Confirm 401 when accessing without a token\n- Confirm successful access with a valid token\n- Test expired token handling\n- Role-based permission tests\n\n**Example**:\n```typescript\ndescribe('Protected Routes', () => {\n  let accessToken: string;\n  let adminToken: string;\n\n  beforeEach(async () => {\n    \u002F\u002F Regular user token\n    const userResponse = await request(app)\n      .post('\u002Fapi\u002Fauth\u002Fregister')\n      .send({\n        email: 'user@example.com',\n        username: 'user',\n        password: 'Password123!'\n      });\n    accessToken = userResponse.body.accessToken;\n\n    \u002F\u002F Admin token\n    const adminResponse = await request(app)\n      .post('\u002Fapi\u002Fauth\u002Fregister')\n      .send({\n        email: 'admin@example.com',\n        username: 'admin',\n        password: 'Password123!'\n      });\n    \u002F\u002F Update role to 'admin' in DB\n    await db.user.update({\n      where: { email: 'admin@example.com' },\n      data: { role: 'admin' }\n    });\n    \u002F\u002F Log in again to get a new token\n    const loginResponse = await request(app)\n      .post('\u002Fapi\u002Fauth\u002Flogin')\n      .send({\n        email: 'admin@example.com',\n        password: 'Password123!'\n      });\n    adminToken = loginResponse.body.accessToken;\n  });\n\n  describe('GET \u002Fapi\u002Fauth\u002Fme', () => {\n    it('should return current user with valid token', async () => {\n      const response = await request(app)\n        .get('\u002Fapi\u002Fauth\u002Fme')\n        .set('Authorization', `Bearer ${accessToken}`);\n\n      expect(response.status).toBe(200);\n      expect(response.body.user.email).toBe('user@example.com');\n    });\n\n    it('should reject request without token', async () => {\n      const response = await request(app)\n        .get('\u002Fapi\u002Fauth\u002Fme');\n\n      expect(response.status).toBe(401);\n    });\n\n    it('should reject request with invalid token', async () => {\n      const response = await request(app)\n        .get('\u002Fapi\u002Fauth\u002Fme')\n        .set('Authorization', 'Bearer invalid-token');\n\n      expect(response.status).toBe(403);\n    });\n  });\n\n  describe('DELETE \u002Fapi\u002Fusers\u002F:id (Admin only)', () => {\n    it('should allow admin to delete user', async () => {\n      const targetUser = await db.user.findUnique({ where: { email: 'user@example.com' } });\n\n      const response = await request(app)\n        .delete(`\u002Fapi\u002Fusers\u002F${targetUser.id}`)\n        .set('Authorization', `Bearer ${adminToken}`);\n\n      expect(response.status).toBe(200);\n    });\n\n    it('should forbid non-admin from deleting user', async () => {\n      const targetUser = await db.user.findUnique({ where: { email: 'user@example.com' } });\n\n      const response = await request(app)\n        .delete(`\u002Fapi\u002Fusers\u002F${targetUser.id}`)\n        .set('Authorization', `Bearer ${accessToken}`);\n\n      expect(response.status).toBe(403);\n    });\n  });\n});\n```\n\n### Step 5: Mocking and Test Isolation\n\nMock external dependencies to isolate tests.\n\n**Tasks**:\n- Mock external APIs\n- Mock email sending\n- Mock file system\n- Mock time-related functions\n\n**Example** (mocking an external API):\n```typescript\n\u002F\u002F src\u002Fservices\u002FemailService.ts\nexport async function sendVerificationEmail(email: string, token: string): Promise\u003Cvoid> {\n  const response = await fetch('https:\u002F\u002Fapi.sendgrid.com\u002Fv3\u002Fmail\u002Fsend', {\n    method: 'POST',\n    headers: { 'Authorization': `Bearer ${process.env.SENDGRID_API_KEY}` },\n    body: JSON.stringify({\n      to: email,\n      subject: 'Verify your email',\n      html: `\u003Ca href=\"https:\u002F\u002Fexample.com\u002Fverify?token=${token}\">Verify\u003C\u002Fa>`\n    })\n  });\n\n  if (!response.ok) {\n    throw new Error('Failed to send email');\n  }\n}\n\n\u002F\u002F src\u002F__tests__\u002Fservices\u002FemailService.test.ts\nimport { sendVerificationEmail } from '..\u002F..\u002Fservices\u002FemailService';\n\n\u002F\u002F Mock fetch\nglobal.fetch = jest.fn();\n\ndescribe('sendVerificationEmail', () => {\n  beforeEach(() => {\n    (fetch as jest.Mock).mockClear();\n  });\n\n  it('should send email successfully', async () => {\n    (fetch as jest.Mock).mockResolvedValueOnce({\n      ok: true,\n      status: 200\n    });\n\n    await expect(sendVerificationEmail('test@example.com', 'token123'))\n      .resolves\n      .toBeUndefined();\n\n    expect(fetch).toHaveBeenCalledWith(\n      'https:\u002F\u002Fapi.sendgrid.com\u002Fv3\u002Fmail\u002Fsend',\n      expect.objectContaining({\n        method: 'POST'\n      })\n    );\n  });\n\n  it('should throw error if email sending fails', async () => {\n    (fetch as jest.Mock).mockResolvedValueOnce({\n      ok: false,\n      status: 500\n    });\n\n    await expect(sendVerificationEmail('test@example.com', 'token123'))\n      .rejects\n      .toThrow('Failed to send email');\n  });\n});\n```\n\n## Output format\n\nDefines the exact format that outputs must follow.\n\n### Basic structure\n\n```\nproject\u002F\n├── src\u002F\n│   ├── __tests__\u002F\n│   │   ├── setup.ts                 # Global test configuration\n│   │   ├── utils\u002F\n│   │   │   └── password.test.ts     # Unit tests\n│   │   ├── services\u002F\n│   │   │   └── emailService.test.ts\n│   │   └── api\u002F\n│   │       ├── auth.test.ts         # Integration tests\n│   │       └── users.test.ts\n│   └── ...\n├── jest.config.js\n└── package.json\n```\n\n### Test run scripts (package.json)\n\n```json\n{\n  \"scripts\": {\n    \"test\": \"jest\",\n    \"test:watch\": \"jest --watch\",\n    \"test:coverage\": \"jest --coverage\",\n    \"test:ci\": \"jest --ci --coverage --maxWorkers=2\"\n  }\n}\n```\n\n### Coverage report\n\n```bash\n$ npm run test:coverage\n\n--------------------------|---------|----------|---------|---------|\nFile                      | % Stmts | % Branch | % Funcs | % Lines |\n--------------------------|---------|----------|---------|---------|\nAll files                 |   92.5  |   88.3   |   95.2  |   92.8  |\n auth\u002F                    |   95.0  |   90.0   |  100.0  |   95.0  |\n  middleware.ts           |   95.0  |   90.0   |  100.0  |   95.0  |\n  routes.ts               |   95.0  |   90.0   |  100.0  |   95.0  |\n utils\u002F                   |   90.0  |   85.0   |   90.0  |   90.0  |\n  password.ts             |   90.0  |   85.0   |   90.0  |   90.0  |\n--------------------------|---------|----------|---------|---------|\n```\n\n## Constraints\n\nRules and prohibitions that must be strictly followed.\n\n### Required rules (MUST)\n\n1. **Test isolation**: Each test must be runnable independently\n   - Reset state with beforeEach\u002FafterEach\n   - Do not depend on test execution order\n\n2. **Clear test names**: The name must convey what the test verifies\n   - ✅ 'should reject duplicate email'\n   - ❌ 'test1'\n\n3. **AAA pattern**: Arrange (setup) - Act (execute) - Assert (verify) structure\n   - Improves readability\n   - Clarifies test intent\n\n### Prohibited (MUST NOT)\n\n1. **No production DB**: Tests must use a separate or in-memory DB\n   - Risk of losing real data\n   - Cannot isolate tests\n\n2. **No real external API calls**: Mock all external services\n   - Removes network dependency\n   - Speeds up tests\n   - Reduces costs\n\n3. **No Sleep\u002FTimeout abuse**: Use fake timers for time-based tests\n   - jest.useFakeTimers()\n   - Prevents test slowdowns\n\n### Security rules\n\n- **No hardcoded secrets**: Never hardcode API keys or passwords in test code\n- **Separate environment variables**: Use .env.test file\n\n## Examples\n\n### Example 1: Python FastAPI tests (Pytest)\n\n**Situation**: Testing a FastAPI REST API\n\n**User request**:\n```\nTest the user API built with FastAPI using pytest.\n```\n\n**Final result**:\n```python\n# tests\u002Fconftest.py\nimport pytest\nfrom fastapi.testclient import TestClient\nfrom sqlalchemy import create_engine\nfrom sqlalchemy.orm import sessionmaker\n\nfrom app.main import app\nfrom app.database import Base, get_db\n\n# In-memory SQLite for tests\nSQLALCHEMY_DATABASE_URL = \"sqlite:\u002F\u002F\u002F.\u002Ftest.db\"\nengine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={\"check_same_thread\": False})\nTestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)\n\n@pytest.fixture(scope=\"function\")\ndef db_session():\n    Base.metadata.create_all(bind=engine)\n    db = TestingSessionLocal()\n    try:\n        yield db\n    finally:\n        db.close()\n        Base.metadata.drop_all(bind=engine)\n\n@pytest.fixture(scope=\"function\")\ndef client(db_session):\n    def override_get_db():\n        try:\n            yield db_session\n        finally:\n            db_session.close()\n\n    app.dependency_overrides[get_db] = override_get_db\n    yield TestClient(app)\n    app.dependency_overrides.clear()\n\n# tests\u002Ftest_auth.py\ndef test_register_user_success(client):\n    response = client.post(\"\u002Fauth\u002Fregister\", json={\n        \"email\": \"test@example.com\",\n        \"username\": \"testuser\",\n        \"password\": \"Password123!\"\n    })\n\n    assert response.status_code == 201\n    assert \"access_token\" in response.json()\n    assert response.json()[\"user\"][\"email\"] == \"test@example.com\"\n\ndef test_register_duplicate_email(client):\n    # First user\n    client.post(\"\u002Fauth\u002Fregister\", json={\n        \"email\": \"test@example.com\",\n        \"username\": \"user1\",\n        \"password\": \"Password123!\"\n    })\n\n    # Duplicate email\n    response = client.post(\"\u002Fauth\u002Fregister\", json={\n        \"email\": \"test@example.com\",\n        \"username\": \"user2\",\n        \"password\": \"Password123!\"\n    })\n\n    assert response.status_code == 409\n    assert \"already exists\" in response.json()[\"detail\"]\n\ndef test_login_success(client):\n    # Register\n    client.post(\"\u002Fauth\u002Fregister\", json={\n        \"email\": \"test@example.com\",\n        \"username\": \"testuser\",\n        \"password\": \"Password123!\"\n    })\n\n    # Login\n    response = client.post(\"\u002Fauth\u002Flogin\", json={\n        \"email\": \"test@example.com\",\n        \"password\": \"Password123!\"\n    })\n\n    assert response.status_code == 200\n    assert \"access_token\" in response.json()\n\ndef test_protected_route_without_token(client):\n    response = client.get(\"\u002Fauth\u002Fme\")\n    assert response.status_code == 401\n\ndef test_protected_route_with_token(client):\n    # Register and get token\n    register_response = client.post(\"\u002Fauth\u002Fregister\", json={\n        \"email\": \"test@example.com\",\n        \"username\": \"testuser\",\n        \"password\": \"Password123!\"\n    })\n    token = register_response.json()[\"access_token\"]\n\n    # Access protected route\n    response = client.get(\"\u002Fauth\u002Fme\", headers={\n        \"Authorization\": f\"Bearer {token}\"\n    })\n\n    assert response.status_code == 200\n    assert response.json()[\"email\"] == \"test@example.com\"\n```\n\n## Best practices\n\n### Quality improvements\n\n1. **TDD (Test-Driven Development)**: Write tests before writing code\n   - Clarifies requirements\n   - Improves design\n   - Naturally achieves high coverage\n\n2. **Given-When-Then pattern**: Write tests in BDD style\n   ```typescript\n   it('should return 404 when user not found', async () => {\n     \u002F\u002F Given: a non-existent user ID\n     const nonExistentId = 'non-existent-uuid';\n\n     \u002F\u002F When: attempting to look up that user\n     const response = await request(app).get(`\u002Fusers\u002F${nonExistentId}`);\n\n     \u002F\u002F Then: 404 response\n     expect(response.status).toBe(404);\n   });\n   ```\n\n3. **Test Fixtures**: Reusable test data\n   ```typescript\n   const validUser = {\n     email: 'test@example.com',\n     username: 'testuser',\n     password: 'Password123!'\n   };\n   ```\n\n### Efficiency improvements\n\n- **Parallel execution**: Speed up tests with Jest's `--maxWorkers` option\n- **Snapshot Testing**: Save snapshots of UI components or JSON responses\n- **Coverage thresholds**: Enforce minimum coverage in jest.config.js\n\n## Common Issues\n\n### Issue 1: Test failures caused by shared state between tests\n\n**Symptom**: Passes individually but fails when run together\n\n**Cause**: DB state shared due to missing beforeEach\u002FafterEach\n\n**Fix**:\n```typescript\nbeforeEach(async () => {\n  await db.migrate.rollback();\n  await db.migrate.latest();\n});\n```\n\n### Issue 2: \"Jest did not exit one second after the test run\"\n\n**Symptom**: Process does not exit after tests complete\n\n**Cause**: DB connections, servers, etc. not cleaned up\n\n**Fix**:\n```typescript\nafterAll(async () => {\n  await db.destroy();\n  await server.close();\n});\n```\n\n### Issue 3: Async test timeout\n\n**Symptom**: \"Timeout - Async callback was not invoked\"\n\n**Cause**: Missing async\u002Fawait or unhandled Promise\n\n**Fix**:\n```typescript\n\u002F\u002F Bad\nit('should work', () => {\n  request(app).get('\u002Fusers');  \u002F\u002F Promise not handled\n});\n\n\u002F\u002F Good\nit('should work', async () => {\n  await request(app).get('\u002Fusers');\n});\n```\n\n## References\n\n### Official docs\n- [Jest Documentation](https:\u002F\u002Fjestjs.io\u002Fdocs\u002Fgetting-started)\n- [Pytest Documentation](https:\u002F\u002Fdocs.pytest.org\u002F)\n- [Supertest GitHub](https:\u002F\u002Fgithub.com\u002Fvisionmedia\u002Fsupertest)\n\n### Learning resources\n- [Testing JavaScript with Kent C. Dodds](https:\u002F\u002Ftestingjavascript.com\u002F)\n- [Test-Driven Development by Example (Kent Beck)](https:\u002F\u002Fwww.amazon.com\u002FTest-Driven-Development-Kent-Beck\u002Fdp\u002F0321146530)\n\n### Tools\n- [Istanbul\u002Fnyc](https:\u002F\u002Fistanbul.js.org\u002F) - code coverage\n- [nock](https:\u002F\u002Fgithub.com\u002Fnock\u002Fnock) - HTTP mocking\n- [faker.js](https:\u002F\u002Ffakerjs.dev\u002F) - test data generation\n\n## Metadata\n\n### Version\n- **Current version**: 1.0.0\n- **Last updated**: 2025-01-01\n- **Compatible platforms**: Claude, ChatGPT, Gemini\n\n### Related skills\n- [api-design](..\u002Fapi-design\u002FSKILL.md): Design APIs alongside tests\n- [authentication-setup](..\u002Fauthentication\u002FSKILL.md): Test authentication systems\n\n### Tags\n`#testing` `#backend` `#Jest` `#Pytest` `#unit-test` `#integration-test` `#TDD` `#API-test`\n",{"name":5,"metadata":57,"description":7},{"tags":58,"platforms":59},"testing, backend, unit-test, integration-test, API-test, Jest, Pytest, TDD","Claude, ChatGPT, Gemini","main","def8531e65114c0fca8fb8551c1871ee0eed705c","skills\u002Faiskillstore\u002Fmarketplace\u002Fsupercent-io\u002Fbackend-testing",24,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub","https:\u002F\u002Fskillshub.wtf","https:\u002F\u002Fgithub.com\u002FComeOnOliver","AI & Blockchain Engineer\r\n","2026-06-24 22:07:19",{"source":8,"skillName":5,"commands":70,"prompt":80,"zipAvailable":81},[71,74,77],{"tool":72,"command":73},"npx","npx skills add ComeOnOliver\u002Fskillshub --skill backend-testing",{"tool":75,"command":76},"bunx","bunx skills add ComeOnOliver\u002Fskillshub --skill backend-testing",{"tool":78,"command":79},"pnpm","pnpm dlx skills add ComeOnOliver\u002Fskillshub --skill backend-testing","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Faiskillstore\u002Fmarketplace\u002Fsupercent-io\u002Fbackend-testing\u002FSKILL.md",true,[83,89],{"path":84,"resourceType":85,"resourceTypeDesc":86,"sizeBytes":87,"sourceHtmlUrl":88},"skill-report.json","REFERENCE","参考文档",11028,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Faiskillstore\u002Fmarketplace\u002Fsupercent-io\u002Fbackend-testing\u002Fskill-report.json",{"path":90,"resourceType":91,"resourceTypeDesc":92,"sizeBytes":93,"sourceHtmlUrl":94},"SKILL.toon","OTHER","其他",697,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Faiskillstore\u002Fmarketplace\u002Fsupercent-io\u002Fbackend-testing\u002FSKILL.toon",[96,108,118,127,133,140],{"skillNo":97,"name":98,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":99,"categories":100,"keywords":104,"publishedAt":52},"01M6FKJN13GMG4KY57J3QD8GY5","model-hierarchy-skill","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzscole\u002Fmodel-hierarchy-skill\u002Fmodel-hierarchy-skill\u002FSKILL.md",[101],{"categoryNo":102,"categoryName":103,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发",[105,106,107],"model","model hierarchy","model hierarchy skill",{"skillNo":109,"name":110,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":111,"categories":112,"keywords":114,"publishedAt":52},"01M6FKJJJ6GCAMR4PPPRD12M5X","zoom-mcp","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fzoom-mcp\u002FSKILL.md",[113],{"categoryNo":102,"categoryName":103,"taxonomyType":21,"taxonomyTypeDesc":22},[115,116,117],"MCP","zoom","zoom mcp",{"skillNo":119,"name":120,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":121,"categories":122,"keywords":123,"publishedAt":52},"01M6FKJE3KYT3KPEZMEEHYKA6K","zoom-apps-sdk","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fzoom-apps-sdk\u002FSKILL.md",[],[124,125,126,116],"sdk","zoom apps sdk","apps",{"skillNo":128,"name":129,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":130,"categories":131,"keywords":132,"publishedAt":52},"01M6FKJ9EDH9Q5VWDF8HAJRSP0","windows","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwindows\u002FSKILL.md",[],[129],{"skillNo":134,"name":135,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":136,"categories":137,"keywords":139,"publishedAt":52},"01M6FKJ666BEEFC3AE9GJ72Q93","whiteboard","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwhiteboard\u002FSKILL.md",[138],{"categoryNo":102,"categoryName":103,"taxonomyType":21,"taxonomyTypeDesc":22},[135],{"skillNo":141,"name":142,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":143,"categories":144,"keywords":145,"publishedAt":52},"01M6FKJ21WFCDGTJQ2Q91T9DVW","websockets","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwebsockets\u002FSKILL.md",[],[142]]