The Kit SDK uses AVA as its testing framework with TypeScript support. Tests are co-located with source code and follow specific naming conventions and patterns.
- TypeScript test files:
{module-name}.test.ts - JavaScript test files:
{module-name}.test.js - Benchmark tests:
{module-name}.benchmark.test.ts - Integration tests:
{module-name}-integration.test.ts
Tests are placed in the same directory as the source code they test, not in a separate test directory.
# Run all tests
pnpm ava
# Watch mode
pnpm ava:watch
# Debug tests
pnpm ava:debug
# Reset cache
pnpm ava:reset
# Type check only (no emit)
pnpm verifyimport ava from 'ava'
// Import your module to test
import { functionToTest } from './module-to-test'
// Import necessary types and utilities
import type { YourTypes } from '../types'// Basic test
ava('test description', async (t) => {
// Arrange
const input = 'test input'
// Act
const result = await functionToTest(input)
// Assert
t.is(result, 'expected output')
})
// Test with setup/teardown
ava.beforeEach(async (t) => {
// Setup code
t.context.testData = { /* test data */ }
})
ava.afterEach.always(async (t) => {
// Cleanup code
})// Equality
t.is(actual, expected)
t.deepEqual(actual, expected)
// Truthiness
t.true(value)
t.false(value)
t.truthy(value)
t.falsy(value)
// Errors
await t.throwsAsync(asyncFunction, { instanceOf: Error, message: 'Expected error' })
await t.notThrowsAsync(asyncFunction)
// Logging
t.log('Debug information')
// Pass/Fail
t.pass('Test passed')
t.fail('Test failed')Many tests create temporary files. Use unique names to avoid conflicts:
import { outputTmpFile } from '../api/kit'
import slugify from 'slugify'
ava('test with temp file', async (t) => {
const name = 'Unique Test Name'
const fileName = slugify(name, { lower: true })
const content = 'test content'
const filePath = await outputTmpFile(`${fileName}.ts`, content)
// Test with the file
// File is automatically cleaned up
})Tests often need to mock environment variables:
// Save original values
const originalEnv = process.env.KENV
// Set test environment
process.env.KENV = '/test/path'
process.env.KIT_CONTEXT = 'workflow'
// Run test...
// Restore (in afterEach)
process.env.KENV = originalEnvFor complex mocking scenarios:
import sinon from 'sinon'
ava.beforeEach(t => {
const mockFunction = sinon.stub()
mockFunction.resolves({ result: 'mocked' })
t.context.mock = mockFunction
})
ava.afterEach.always(t => {
sinon.restore()
})For streaming tests:
async function* mockGenerator() {
yield { type: 'text-delta', textDelta: 'chunk1' }
yield { type: 'text-delta', textDelta: 'chunk2' }
yield { type: 'finish', finishReason: 'stop' }
}
ava('stream test', async (t) => {
let result = ''
for await (const chunk of mockGenerator()) {
if (chunk.type === 'text-delta') {
result += chunk.textDelta
}
}
t.is(result, 'chunk1chunk2')
})- Test Isolation: Each test should be independent and not rely on other tests
- Descriptive Names: Use clear, descriptive test names that explain what is being tested
- Co-location: Place tests next to the code they test
- Unique Identifiers: Use unique names for temp files and test data to avoid conflicts
- Cleanup: Always clean up resources (files, mocks, etc.) in
afterEach.always - Type Safety: Use TypeScript types for better test reliability
- Serial Tests: Use
ava.serialwhen tests must run in order or share state
- Core: Parser, format, utils, scriptlets (most heavily tested)
- API: npm, onepassword integrations
- CLI: Command-line interface functionality
- Globals: Custom global functions and utilities
- Workers: Background processing and caching
- Setup: Installation and configuration
- Use
t.log()for debug output - Run specific test file:
pnpm ava src/core/utils.test.ts - Use
ava.onlyto run a single test - Check test config in
/test/ava.config.mjs
Some tests are platform-specific:
if (process.platform !== 'win32') {
ava('unix-specific test', async (t) => {
// Test code
})
}This ensures tests pass across all supported platforms.