Skip to main content

Testing

Guide to running and writing tests for AgentVault.

Test Framework​

AgentVault uses Vitest for testing.

Running Tests​

All Tests​

npm run test

Watch Mode​

npm run test:watch

Specific Test File​

npm run test tests/unit/format.test.ts

Filter Tests​

# Run tests matching pattern
npm run test -- --grep "wallet"

Coverage​

npm run test -- --coverage

Test Structure​

tests/
├── unit/ # Unit tests
│ ├── format.test.ts
│ └── validation.test.ts
├── cli/ # CLI command tests
│ ├── init.test.ts
│ ├── deploy.test.ts
│ └── wallet.test.ts
├── deployment/ # Deployment tests
│ └── icpClient.test.ts
├── icp/ # ICP integration tests
│ └── actor.test.ts
└── integration/ # Full integration tests
└── workflow.test.ts

Test Statistics​

AgentVault currently runs 1,599 tests across 92 test files.

Run npm test for the current figure rather than relying on this number — it drifts. (It previously read "508 tests across 31 test files", roughly a third of the real suite.)

Coverage is uneven, and skewed away from some of the higher-risk code. The vault, wallet, security and hypervault suites are substantial, but several subsystems have no direct tests at all — fault-tolerance, metrics, network, pilot, testing, orchestration, and backbone (the last of which is a published entry point). The webapp/ and site/ packages have no test files.

Writing Tests​

Basic Test​

import { describe, it, expect } from 'vitest';

describe('MyModule', () => {
it('should do something', () => {
expect(myFunction()).toBe('expected');
});
});

Async Test​

import { describe, it, expect } from 'vitest';

describe('AsyncModule', () => {
it('should handle async operations', async () => {
const result = await asyncFunction();
expect(result).toBeDefined();
});
});

Mocking​

import { describe, it, expect, vi } from 'vitest';

describe('MockedModule', () => {
it('should use mocked function', () => {
const mock = vi.fn().mockReturnValue('mocked');
expect(mock()).toBe('mocked');
expect(mock).toHaveBeenCalled();
});
});

Setup and Teardown​

import { describe, it, expect, beforeEach, afterEach } from 'vitest';

describe('SetupModule', () => {
let resource: Resource;

beforeEach(() => {
resource = createResource();
});

afterEach(() => {
resource.cleanup();
});

it('should use resource', () => {
expect(resource.isReady()).toBe(true);
});
});

CLI Command Tests​

Testing Commands​

import { describe, it, expect } from 'vitest';
import { Command } from 'commander';
import { initCommand } from '../../cli/commands/init';

describe('init command', () => {
it('should have correct name', () => {
expect(initCommand.name()).toBe('init');
});

it('should have required options', () => {
const options = initCommand.options;
expect(options.some(o => o.long === '--force')).toBe(true);
});
});

Integration Tests​

ICP Client Tests​

import { describe, it, expect, beforeAll } from 'vitest';
import { ICPClient } from '../../src/deployment/icpClient';

describe('ICPClient', () => {
let client: ICPClient;

beforeAll(async () => {
client = new ICPClient({ network: 'local' });
});

it('should connect to local network', async () => {
const status = await client.getStatus();
expect(status).toBeDefined();
});
});

Test Configuration​

vitest.config.ts​

import { defineConfig } from 'vitest/config';

export default defineConfig({
test: {
globals: true,
environment: 'node',
include: ['tests/**/*.test.ts'],
exclude: ['node_modules', 'dist'],
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
},
},
});

Best Practices​

Test Naming​

// Good: Descriptive names
it('should throw error for invalid canister ID format', () => {
expect(() => validateCanisterId('invalid')).toThrow();
});

// Bad: Vague names
it('works', () => {
expect(fn()).toBe(true);
});

Arrange-Act-Assert​

it('should calculate correct balance', () => {
// Arrange
const amount = 1000000000n;
const decimals = 9;

// Act
const result = formatBalance(amount, decimals);

// Assert
expect(result).toBe('1');
});

One Assertion Per Test (Generally)​

// Good: Focused test
it('should return correct status', () => {
const agent = createAgent();
expect(agent.getStatus()).toBe('idle');
});

// Also acceptable: Related assertions
it('should initialize with default values', () => {
const agent = createAgent();
expect(agent.getStatus()).toBe('idle');
expect(agent.getTasks()).toEqual([]);
});

Avoid Magic Values​

// Good: Named constants
const CANISTER_ID = 'abcde-aaaab';
const EXPECTED_BALANCE = '1.0';

it('should query balance', async () => {
const balance = await getBalance(CANISTER_ID);
expect(balance).toBe(EXPECTED_BALANCE);
});

Debugging Tests​

Debug Single Test​

# Run with Node debugger
node --inspect-brk node_modules/.bin/vitest run --no-threads

Console Output​

it('should debug', () => {
console.log('Debug output:', result);
expect(result).toBeDefined();
});

Continuous Integration​

Tests run automatically on:

  • Pull requests
  • Pushes to main branch

CI configuration:

# .github/workflows/test.yml
- name: Run tests
run: npm run test

Adding New Tests​

  1. Create test file in appropriate tests/ subdirectory
  2. Follow naming convention: *.test.ts
  3. Import from vitest
  4. Run tests to verify
# Create new test file
touch tests/unit/newFeature.test.ts

# Run new test
npm run test tests/unit/newFeature.test.ts