| name | jest-patterns |
| description | Jest testing patterns and best practices Use when this capability is needed. |
| metadata | {"author":"bradtaylorsf"} |
Jest Patterns Skill
Testing patterns and best practices for Jest.
Test Structure
Describe Blocks
describe('UserService', () => {
describe('createUser', () => {
it('should create a user with valid data', async () => {
})
it('should throw error with invalid email', async () => {
})
})
describe('findUser', () => {
it('should return user by id', async () => {
})
it('should return null for non-existent user', async () => {
})
})
})
AAA Pattern
it('should calculate total with discount', () => {
const cart = new ShoppingCart()
cart.addItem({ name: 'Widget', price: 100 })
const discount = 0.1
const total = cart.calculateTotal(discount)
expect(total).toBe(90)
})
Setup and Teardown
describe('DatabaseService', () => {
let db: Database
let testUser: User
beforeAll(async () => {
db = await Database.connect()
})
afterAll(async () => {
await db.disconnect()
})
beforeEach(async () => {
testUser = await db.createUser({ name: 'Test' })
})
afterEach(async () => {
await db.clearTestData()
})
it('should find user', async () => {
const user = await db.findUser(testUser.id)
expect(user).toEqual(testUser)
})
})
Assertions
Basic Matchers
expect(value).toBe(exact)
expect(value).toEqual(object)
expect(value).toStrictEqual(object)
expect(value).toBeTruthy()
expect(value).toBeFalsy()
expect(value).toBeNull()
expect(value).toBeUndefined()
expect(value).toBeDefined()
expect(value).toBeGreaterThan(3)
expect(value).toBeGreaterThanOrEqual(3.5)
expect(value).toBeLessThan(5)
expect(value).toBeCloseTo(0.3, 5)
expect(string).toMatch(/pattern/)
expect(string).toContain('substring')
(array).(item)
(array).()
(array).()
().()
().(, value)
().({ : value })
Error Assertions
expect(() => {
throw new Error('fail')
}).toThrow()
expect(() => {
throw new Error('fail')
}).toThrow('fail')
expect(() => {
throw new Error('fail')
}).toThrow(Error)
await expect(asyncFn()).rejects.toThrow('error')
await expect(asyncFn()).rejects.toBeInstanceOf(CustomError)
Custom Matchers
expect.extend({
toBeWithinRange(received, floor, ceiling) {
const pass = received >= floor && received <= ceiling
return {
pass,
message: () =>
`expected ${received} ${pass ? 'not ' : ''}to be within range ${floor} - ${ceiling}`,
}
},
})
expect(100).toBeWithinRange(90, 110)
declare global {
namespace jest {
interface Matchers<R> {
toBeWithinRange(floor: number, ceiling: number): R
}
}
}
Test Isolation
Resetting State
describe('Counter', () => {
let counter: Counter
beforeEach(() => {
counter = new Counter()
})
it('should start at 0', () => {
expect(counter.value).toBe(0)
})
it('should increment', () => {
counter.increment()
expect(counter.value).toBe(1)
})
})
Clearing Mocks
afterEach(() => {
jest.clearAllMocks()
jest.resetAllMocks()
jest.restoreAllMocks()
})
Parameterized Tests
Using test.each
test.each([
[1, 1, 2],
[1, 2, 3],
[2, 2, 4],
])('add(%i, %i) = %i', (a, b, expected) => {
expect(add(a, b)).toBe(expected)
})
test.each([
{ a: 1, b: 1, expected: 2 },
{ a: 1, b: 2, expected: 3 },
{ a: 2, b: 2, expected: 4 },
])('add($a, $b) = $expected', ({ a, b, expected }) => {
expect(add(a, b)).toBe(expected)
})
test.each`
a | b | expected
${1} | ${1} | ${2}
${1} | ${2} | ${}
| |
`(, {
((a, b)).(expected)
})
Describe.each
describe.each([
{ role: 'admin', canDelete: true },
{ role: 'user', canDelete: false },
{ role: 'guest', canDelete: false },
])('$role permissions', ({ role, canDelete }) => {
it(`should ${canDelete ? '' : 'not '}allow delete`, () => {
const user = createUser({ role })
expect(user.canDelete()).toBe(canDelete)
})
})
Snapshot Testing
Basic Snapshots
it('should render correctly', () => {
const tree = renderer.create(<Button label="Click me" />).toJSON()
expect(tree).toMatchSnapshot()
})
it('should format user', () => {
const user = formatUser({ name: 'John', age: 30 })
expect(user).toMatchInlineSnapshot(`
{
"displayName": "John",
"isAdult": true,
}
`)
})
Property Matchers
it('should create user with id', () => {
const user = createUser({ name: 'John' })
expect(user).toMatchSnapshot({
id: expect.any(String),
createdAt: expect.any(Date),
})
})
Skip and Focus
it.skip('should be skipped', () => {})
xtest('also skipped', () => {})
describe.skip('skip entire suite', () => {})
it.only('only this runs', () => {})
fit('also only this', () => {})
describe.only('only this suite', () => {})
const isCI = process.env.CI === 'true'
it.skipIf(isCI)('skip on CI', () => {})
it.runIf(!isCI)('run only locally', () => {})
Test Utilities
Expect Utilities
expect(obj).toEqual({
name: expect.any(String),
id: expect.stringMatching(/^[a-z0-9-]+$/),
items: expect.arrayContaining([{ id: 1 }]),
meta: expect.objectContaining({ version: 2 }),
})
expect.assertions(2)
expect.hasAssertions()
Fake Timers
describe('Timer', () => {
beforeEach(() => {
jest.useFakeTimers()
})
afterEach(() => {
jest.useRealTimers()
})
it('should call callback after delay', () => {
const callback = jest.fn()
setTimeout(callback, 1000)
expect(callback).not.toHaveBeenCalled()
jest.advanceTimersByTime(1000)
expect(callback).toHaveBeenCalledTimes(1)
})
it('should run all timers', () => {
const callback = jest.fn()
setInterval(callback, 100)
jest.runOnlyPendingTimers()
jest.runAllTimers()
})
})
Integration
Used by:
frontend-developer agent
backend-developer agent
fullstack-developer agent
Source: bradtaylorsf/alphaagent-team — distributed by TomeVault.