// This skill provides comprehensive guidance for setting up and configuring test environments for VS Code extension projects. Use when initializing a new test infrastructure, configuring test runners (Mocha, Jest), setting up CI/CD test pipelines, integrating coverage tools (c8, nyc), or troubleshooting test configuration issues.
| name | vscode-test-setup |
| description | This skill provides comprehensive guidance for setting up and configuring test environments for VS Code extension projects. Use when initializing a new test infrastructure, configuring test runners (Mocha, Jest), setting up CI/CD test pipelines, integrating coverage tools (c8, nyc), or troubleshooting test configuration issues. |
This skill enables rapid and reliable test environment setup for VS Code extension projects. It covers test framework configuration, CI/CD integration, coverage tooling, and best practices for maintainable test infrastructure.
# Core testing dependencies
npm install --save-dev \
@vscode/test-cli \
@vscode/test-electron \
mocha \
chai \
sinon \
sinon-chai \
@types/mocha \
@types/chai \
@types/sinon
# Coverage tools
npm install --save-dev c8
# Optional: Additional assertions
npm install --save-dev chai-as-promised
Run the setup script to create all necessary configuration files:
# Execute from skill directory
python scripts/setup-test-env.py --project-path /path/to/extension
Or manually create the following files:
const { defineConfig } = require('@vscode/test-cli');
module.exports = defineConfig({
files: 'out/test/**/*.test.js',
version: 'stable',
workspaceFolder: './test-fixtures',
launchArgs: [
'--disable-extensions',
'--disable-workspace-trust'
],
mocha: {
timeout: 20000,
ui: 'bdd',
color: true,
retries: process.env.CI ? 2 : 0
}
});
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "commonjs",
"outDir": "./out/test",
"rootDir": "./src",
"types": ["mocha", "node", "chai", "sinon"]
},
"include": [
"src/test/**/*.ts"
]
}
{
"scripts": {
"compile": "tsc -p ./",
"compile:test": "tsc -p ./tsconfig.test.json",
"watch": "tsc -watch -p ./",
"pretest": "npm run compile && npm run compile:test",
"test": "vscode-test",
"test:unit": "mocha out/test/unit/**/*.test.js --timeout 5000",
"test:integration": "vscode-test",
"test:coverage": "c8 npm run test:unit",
"test:watch": "mocha out/test/unit/**/*.test.js --watch --timeout 5000",
"tdd:red": "npm run test:unit -- --grep 'RED:'",
"tdd:green": "npm run test:unit",
"tdd:refactor": "npm run lint && npm run test:unit",
"tdd:quality-gate": "npm run test:coverage && npm run lint"
}
}
src/
โโโ test/
โ โโโ unit/ # Pure unit tests (no VS Code API)
โ โ โโโ setup.ts # Unit test setup
โ โ โโโ utils.test.ts
โ โ โโโ models.test.ts
โ โโโ integration/ # Tests requiring VS Code API
โ โ โโโ setup.ts # Integration test setup
โ โ โโโ extension.test.ts
โ โ โโโ commands.test.ts
โ โโโ e2e/ # End-to-end tests
โ โ โโโ activation.test.ts
โ โโโ fixtures/ # Test data
โ โ โโโ sample-workspace/
โ โ โโโ test-data.json
โ โโโ helpers/ # Shared test utilities
โ โโโ vscode-mock.ts
โ โโโ async-helpers.ts
โ โโโ test-utils.ts
test-fixtures/ # VS Code test workspace
โโโ .vscode/
โโโ settings.json
{
"require": ["ts-node/register"],
"extension": ["ts"],
"spec": "src/test/unit/**/*.test.ts",
"timeout": 5000,
"ui": "bdd",
"color": true,
"reporter": "spec",
"exit": true
}
import * as chai from 'chai';
import * as sinonChai from 'sinon-chai';
// Configure chai
chai.use(sinonChai);
// Configure chai-as-promised for async tests
import chaiAsPromised = require('chai-as-promised');
chai.use(chaiAsPromised);
export { expect } from 'chai';
export { default as sinon } from 'sinon';
import * as path from 'path';
import * as Mocha from 'mocha';
import { glob } from 'glob';
export async function run(): Promise<void> {
const mocha = new Mocha({
ui: 'bdd',
color: true,
timeout: 20000,
retries: process.env.CI ? 2 : 0
});
const testsRoot = path.resolve(__dirname, '.');
const files = await glob('**/*.test.js', { cwd: testsRoot });
files.forEach((f) => mocha.addFile(path.resolve(testsRoot, f)));
return new Promise((resolve, reject) => {
mocha.run((failures) => {
if (failures > 0) {
reject(new Error(`${failures} tests failed.`));
} else {
resolve();
}
});
});
}
/** @type {import('jest').Config} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
roots: ['<rootDir>/src/test/unit'],
testMatch: ['**/*.test.ts'],
moduleFileExtensions: ['ts', 'js', 'json'],
collectCoverageFrom: [
'src/**/*.ts',
'!src/test/**',
'!**/*.d.ts'
],
coverageDirectory: 'coverage',
coverageReporters: ['text', 'lcov', 'html'],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80
}
},
setupFilesAfterEnv: ['<rootDir>/src/test/unit/setup.ts'],
moduleNameMapper: {
'^vscode$': '<rootDir>/src/test/helpers/vscode-mock.ts'
}
};
{
"c8": {
"include": ["src/**/*.ts"],
"exclude": [
"src/test/**",
"**/*.d.ts",
"**/node_modules/**"
],
"reporter": ["text", "html", "lcov"],
"all": true,
"clean": true,
"check-coverage": true,
"branches": 80,
"functions": 80,
"lines": 80,
"statements": 80,
"report-dir": "./coverage"
}
}
{
"extends": "@istanbuljs/nyc-config-typescript",
"include": ["src/**/*.ts"],
"exclude": ["src/test/**", "**/*.d.ts"],
"reporter": ["text", "html", "lcov"],
"all": true,
"check-coverage": true,
"branches": 80,
"functions": 80,
"lines": 80,
"statements": 80
}
name: Test
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test:
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
vscode-version: ['stable', 'insiders']
fail-fast: false
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Compile
run: npm run compile
- name: Run unit tests
run: npm run test:unit
- name: Run integration tests (Linux)
if: runner.os == 'Linux'
run: xvfb-run -a npm run test:integration
env:
VSCODE_TEST_VERSION: ${{ matrix.vscode-version }}
- name: Run integration tests (Windows/macOS)
if: runner.os != 'Linux'
run: npm run test:integration
env:
VSCODE_TEST_VERSION: ${{ matrix.vscode-version }}
- name: Upload coverage
if: matrix.os == 'ubuntu-latest' && matrix.vscode-version == 'stable'
uses: codecov/codecov-action@v4
with:
file: ./coverage/lcov.info
fail_ci_if_error: true
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- run: npm run lint
name: TDD Quality Gate
on:
push:
branches: [main]
pull_request:
jobs:
tdd-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Compile
run: npm run compile
- name: Run TDD Quality Gate
run: npm run tdd:quality-gate
- name: Check coverage thresholds
run: npx c8 check-coverage
- name: Generate coverage report
run: npx c8 report --reporter=text
- name: Upload coverage report
uses: actions/upload-artifact@v4
with:
name: coverage-report
path: coverage/
{
"version": "0.2.0",
"configurations": [
{
"name": "Run Extension",
"type": "extensionHost",
"request": "launch",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}"
],
"outFiles": ["${workspaceFolder}/out/**/*.js"],
"preLaunchTask": "npm: compile"
},
{
"name": "Run Integration Tests",
"type": "extensionHost",
"request": "launch",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}",
"--extensionTestsPath=${workspaceFolder}/out/test/integration"
],
"outFiles": ["${workspaceFolder}/out/**/*.js"],
"preLaunchTask": "npm: compile"
},
{
"name": "Run Unit Tests",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/node_modules/mocha/bin/_mocha",
"args": [
"--timeout",
"5000",
"out/test/unit/**/*.test.js"
],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"preLaunchTask": "npm: compile"
},
{
"name": "Debug Current Test File",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/node_modules/mocha/bin/_mocha",
"args": [
"--timeout",
"999999",
"--grep",
"",
"${file}"
],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"preLaunchTask": "npm: compile"
}
]
}
{
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "compile",
"problemMatcher": "$tsc",
"group": "build",
"label": "npm: compile"
},
{
"type": "npm",
"script": "watch",
"problemMatcher": "$tsc-watch",
"isBackground": true,
"group": "build",
"label": "npm: watch"
},
{
"type": "npm",
"script": "test:unit",
"problemMatcher": [],
"group": "test",
"label": "npm: test:unit"
},
{
"type": "npm",
"script": "test:coverage",
"problemMatcher": [],
"group": "test",
"label": "npm: test:coverage"
}
]
}
{
"editor.formatOnSave": false,
"editor.tabSize": 2,
"files.autoSave": "off",
"terminal.integrated.defaultProfile.linux": "bash",
"terminal.integrated.defaultProfile.osx": "zsh",
"terminal.integrated.defaultProfile.windows": "PowerShell"
}
// src/test/helpers/test-data-factory.ts
import * as vscode from 'vscode';
export class TestDataFactory {
static createTerminalOptions(
overrides: Partial<vscode.TerminalOptions> = {}
): vscode.TerminalOptions {
return {
name: 'Test Terminal',
cwd: '/tmp',
env: { TEST_ENV: 'true' },
...overrides
};
}
static createWebviewContent(title: string): string {
return `<!DOCTYPE html>
<html>
<head><title>${title}</title></head>
<body><h1>Test Content</h1></body>
</html>`;
}
static createMockTerminalState(): any {
return {
id: 1,
name: 'Terminal 1',
processState: 'running',
scrollback: 'mock scrollback content',
cwd: '/home/user'
};
}
static createMockSessionData(): any {
return {
version: 1,
terminals: [this.createMockTerminalState()],
savedAt: Date.now()
};
}
}
Symptoms: Tests pass locally but timeout in GitHub Actions
Solutions:
xvfb-run for Linux headless testing# GitHub Actions
- name: Run tests (Linux)
if: runner.os == 'Linux'
run: xvfb-run -a npm run test:integration
Symptoms: ERR_REQUIRE_ESM when importing chai or other modules
Solutions:
// Use dynamic import
const chai = await import('chai');
const { expect } = chai;
Symptoms: Cannot find module 'vscode'
Solutions:
// jest.config.js
moduleNameMapper: {
'^vscode$': '<rootDir>/src/test/helpers/vscode-mock.ts'
}
Symptoms: Coverage shows 0% or missing files
Solutions:
{
"compilerOptions": {
"sourceMap": true,
"inlineSources": true
}
}
Symptoms: Tests fail intermittently
Solutions:
// Bad
setTimeout(() => expect(value).to.equal(1), 100);
// Good
await waitForCondition(() => value === 1);
expect(value).to.equal(1);
For detailed reference documentation, see:
references/framework-comparison.md - Mocha vs Jest comparisonreferences/ci-templates.md - CI/CD pipeline templatesscripts/setup-test-env.py - Automated environment setup