Writing Tests
You write tests that are clear, maintainable, and thorough. You optimize for readability and reliability. Tests should be easy to understand and cover both typical use cases and edge cases.
Setup
- Use Vitest for most tests. Vitest is our primary testing framework.
- No globals. Always explicitly import describe, it, and expect from vitest in every test file.
- File naming conventions:
- Unit/integration test files end with .test.ts.
- Each test file matches the name of the file it tests. Example: If the code is in custom-function.ts, the test file should be named custom-function.test.ts.
- The test file is located in the same folder as the file under test. This keeps code and tests closely related, improving discoverability and maintainability.
- Minimize mocking. Only mock when absolutely necessary. Prefer refactoring the code under test to make mocking unnecessary. Aim for simpler, pure functions that are easier to test without mocks.
- Do not use stubs
- Every test file has a single top-level describe().
- The top-level describe() matches the file name under test. Example: describe('custom-function') for custom-function.test.ts.
- Do not use nested describe() blocks. Keep tests flat within the single describe().
- Use it() for individual tests.
- Keep test descriptions concise and direct.
- Do not start test descriptions with "should."
✅ it('generates a slug from the title')
❌ it('should generate a slug from the title')
Testing Vue Components
- Don't rely on markup for assertions.
- Avoid testing the exact structure of the DOM unless necessary.
- Do not rely on Tailwind CSS classes in assertions.
- Focus on testing behavior, outputs, and user interactions instead of implementation details.
Playwright Tests
- Use Playwright for limited end-to-end testing.
- Playwright tests live in /playwright/test/.
- Files end with .spec.ts.
- Example file: @local.spec.ts.
- Be selective. We intentionally limit the number of Playwright tests to avoid maintenance overhead.
Assertions
- Use strict, precise assertions. Prefer
toBe, toEqual, and toStrictEqual over loose checks.
- Do not use
toBeDefined, toBeTruthy, toHaveLength, or toMatchObject when you can assert the exact value instead.
- Do not use
expect.arrayContaining or expect.objectContaining. Assert the full expected value.
- Use
toStrictEqual when checking objects or arrays to catch extra or missing properties.
- Use
toBe for primitives (strings, numbers, booleans).
- Use
toBeUndefined only when the expected value is genuinely undefined.
// ❌ BAD - vague, does not catch wrong values
expect(result).toBeDefined()
expect(items).toHaveLength(2)
expect(user).toMatchObject({ name: 'Alice' })
// ✅ GOOD - exact, catches regressions
expect(result).toBe('expected-value')
expect(items).toStrictEqual([{ id: 1 }, { id: 2 }])
expect(user).toStrictEqual({ name: 'Alice', role: 'admin' })
Style & Best Practices
- Clarity first. Write tests that are easy to read and understand, even for someone unfamiliar with the code.
- Think like a QA engineer.
- Cover all important code paths.
- Test both the happy path and error handling.
- Add tests for edge cases and potential failure scenarios.
- Comments are welcome when they add value.
- Use comments to explain why a test exists, not what it's doing.
- Avoid repeating what the code already makes obvious.
Example Test File Structure
/src
/lib
custom-lib.ts
custom-lib.test.ts
import { describe, it, expect } from 'vitest'
import { generateSlug, doSomething } from './custom-lib'
describe('generateSlug', () => {
it('generates a slug from the title', () => {
const result = generateSlug('Hello World')
expect(result).toBe('hello-world')
})
it('handles empty input gracefully', () => {
const result = generateSlug('')
expect(result).toBe('')
})
})
describe('doSomething', () => {
it('does something really well', () => {
const result = doSomething('Hello World')
expect(result).toBe('hello-world')
})
})
1---2name: tests3description: Write clear, maintainable Vitest and Playwright tests with precise assertions, consistent structure, and strong behavioral coverage.4---56# Writing Tests78You write tests that are clear, maintainable, and thorough. You optimize for readability and reliability. Tests should be easy to understand and cover both typical use cases and edge cases.910## Setup1112* Use Vitest for most tests. Vitest is our primary testing framework.13* No globals. Always explicitly import describe, it, and expect from vitest in every test file.14* File naming conventions:15* Unit/integration test files end with .test.ts.16* Each test file matches the name of the file it tests. Example: If the code is in custom-function.ts, the test file should be named custom-function.test.ts.17* The test file is located in the same folder as the file under test. This keeps code and tests closely related, improving discoverability and maintainability.18* Minimize mocking. Only mock when absolutely necessary. Prefer refactoring the code under test to make mocking unnecessary. Aim for simpler, pure functions that are easier to test without mocks.19* Do not use stubs20* Every test file has a single top-level describe().21* The top-level describe() matches the file name under test. Example: describe('custom-function') for custom-function.test.ts.22* Do not use nested describe() blocks. Keep tests flat within the single describe().23* Use it() for individual tests.24* Keep test descriptions concise and direct.25* Do not start test descriptions with "should."26 ✅ it('generates a slug from the title')27 ❌ it('should generate a slug from the title')2829## Testing Vue Components3031* Don't rely on markup for assertions.32* Avoid testing the exact structure of the DOM unless necessary.33* Do not rely on Tailwind CSS classes in assertions.34* Focus on testing behavior, outputs, and user interactions instead of implementation details.3536## Playwright Tests3738* Use Playwright for limited end-to-end testing.39* Playwright tests live in /playwright/test/.40* Files end with .spec.ts.41* Example file: @local.spec.ts.42* Be selective. We intentionally limit the number of Playwright tests to avoid maintenance overhead.4344## Assertions4546* Use strict, precise assertions. Prefer `toBe`, `toEqual`, and `toStrictEqual` over loose checks.47* Do not use `toBeDefined`, `toBeTruthy`, `toHaveLength`, or `toMatchObject` when you can assert the exact value instead.48* Do not use `expect.arrayContaining` or `expect.objectContaining`. Assert the full expected value.49* Use `toStrictEqual` when checking objects or arrays to catch extra or missing properties.50* Use `toBe` for primitives (strings, numbers, booleans).51* Use `toBeUndefined` only when the expected value is genuinely undefined.5253```ts54// ❌ BAD - vague, does not catch wrong values55expect(result).toBeDefined()56expect(items).toHaveLength(2)57expect(user).toMatchObject({ name: 'Alice' })5859// ✅ GOOD - exact, catches regressions60expect(result).toBe('expected-value')61expect(items).toStrictEqual([{ id: 1 }, { id: 2 }])62expect(user).toStrictEqual({ name: 'Alice', role: 'admin' })63```6465## Style & Best Practices6667* Clarity first. Write tests that are easy to read and understand, even for someone unfamiliar with the code.68* Think like a QA engineer.69* Cover all important code paths.70* Test both the happy path and error handling.71* Add tests for edge cases and potential failure scenarios.72* Comments are welcome when they add value.73* Use comments to explain why a test exists, not what it's doing.74* Avoid repeating what the code already makes obvious.7576## Example Test File Structure7778```79/src80 /lib81 custom-lib.ts82 custom-lib.test.ts83```8485```ts86import { describe, it, expect } from 'vitest'87import { generateSlug, doSomething } from './custom-lib'8889describe('generateSlug', () => {90 it('generates a slug from the title', () => {91 const result = generateSlug('Hello World')92 expect(result).toBe('hello-world')93 })9495 it('handles empty input gracefully', () => {96 const result = generateSlug('')97 expect(result).toBe('')98 })99})100101describe('doSomething', () => {102 it('does something really well', () => {103 const result = doSomething('Hello World')104 expect(result).toBe('hello-world')105 })106})107```