Skip to main content

Overview

VS Code has a comprehensive test suite covering unit tests, integration tests, extension tests, and smoke tests. Understanding how to run and write tests is essential for contributing to VS Code.

Unit Tests

Test individual components in isolation

Integration Tests

Test interactions between components

Extension Tests

Test built-in extension functionality

Test Infrastructure

Test Locations

Unit tests are located alongside source files:
src/
  vs/
    editor/
      common/
        model.ts
        test/
          model.test.ts      # Unit tests
Test Files: *.test.ts in test/ subdirectoriesEntry Point: test/unit/electron/index.js

Running Unit Tests

Command Line

Run all unit tests:
./scripts/test.sh
Run specific test file:
./scripts/test.sh --run src/vs/editor/test/common/model/model.test.ts
Filter tests by name:
./scripts/test.sh --grep "should validate range"
Run with debugger:
./scripts/test.sh --inspect-brk

Environment Setup

The test script automatically:
  1. Checks for node_modules and runs npm i if needed
  2. Downloads Electron (unless VSCODE_SKIP_PRELAUNCH=1)
  3. Runs tests with Electron binary
  4. Saves crash reports to .build/crashes
Tests run in Electron to test both Node.js and browser APIs in the same environment as VS Code.

Running Integration Tests

Full Integration Test Suite

./scripts/test-integration.sh
This script runs:
  1. Node.js integration tests (**/*.integrationTest.js)
  2. Extension API tests (folder and workspace)
  3. Colorize tests
  4. Terminal suggest tests
  5. Language feature tests (TypeScript, Markdown, etc.)
  6. Git tests
  7. Standalone tests (CSS, HTML)

Test Configuration

Integration tests use special arguments:
API_TESTS_EXTRA_ARGS="
  --disable-telemetry
  --disable-experiments
  --skip-welcome
  --skip-release-notes
  --crash-reporter-directory=$VSCODECRASHDIR
  --logsPath=$VSCODELOGSDIR
  --no-cached-data
  --disable-updates
  --use-inmemory-secretstorage
  --disable-extensions
  --disable-workspace-trust
  --user-data-dir=$VSCODEUSERDATADIR
"

Extension Tests

Using @vscode/test-cli

The repository uses @vscode/test-cli for running extension tests, configured in .vscode-test.js:
const extensions = [
  {
    label: 'markdown-language-features',
    workspaceFolder: 'extensions/markdown-language-features/test-workspace',
    mocha: { timeout: 60_000 }
  },
  {
    label: 'vscode-api-tests-folder',
    extensionDevelopmentPath: 'extensions/vscode-api-tests',
    workspaceFolder: 'extensions/vscode-api-tests/testWorkspace',
    files: 'extensions/vscode-api-tests/out/singlefolder-tests/**/*.test.js',
  }
];

Running Extension Tests

# Run tests for specific extension
npm run test-extension -- -l markdown-language-features

# Run with specific label
npm run test-extension -- -l vscode-api-tests-folder

Available Extension Tests

Test the VS Code Extension API:
# Single folder tests
npm run test-extension -- -l vscode-api-tests-folder

# Workspace tests
npm run test-extension -- -l vscode-api-tests-workspace
Test Location: extensions/vscode-api-tests/out/Coverage: Commands, languages, workspace, debug API, etc.

Web Tests

Test VS Code in browser environments:
./scripts/test-web-integration.sh

Debugging Tests

VS Code Launch Configurations

Use the pre-configured debug configurations:
Configuration: Debug Unit Tests
  1. Open Run and Debug view (Cmd/Ctrl + Shift + D)
  2. Select “Debug Unit Tests”
  3. Press F5
This runs all unit tests with the debugger attached.

Command Line Debugging

# Pause at start
./scripts/test.sh --inspect-brk

# Attach debugger
./scripts/test.sh --inspect

Writing Tests

Unit Test Structure

VS Code uses Mocha with a TDD-style interface:
import * as assert from 'assert';
import { Model } from 'vs/editor/common/model';

suite('Editor Model', () => {
    test('should create model', () => {
        const model = new Model('hello world', 'text/plain');
        assert.strictEqual(model.getValue(), 'hello world');
    });

    test('should handle edits', () => {
        const model = new Model('hello', 'text/plain');
        model.applyEdits([{
            range: { startLineNumber: 1, startColumn: 6, endLineNumber: 1, endColumn: 6 },
            text: ' world'
        }]);
        assert.strictEqual(model.getValue(), 'hello world');
    });
});

Test Organization

1

Use suite() for grouping

suite('Editor', () => {
    suite('Model', () => {
        test('basic operations', () => { /* ... */ });
    });

    suite('View', () => {
        test('rendering', () => { /* ... */ });
    });
});
2

Use test() for individual tests

test('should do something specific', () => {
    // Arrange
    const input = createInput();

    // Act
    const result = processInput(input);

    // Assert
    assert.strictEqual(result, expected);
});
3

Use setup() and teardown()

suite('FileService', () => {
    let service: FileService;

    setup(() => {
        service = new FileService();
    });

    teardown(() => {
        service.dispose();
    });

    test('should read file', async () => {
        const content = await service.readFile(uri);
        assert.ok(content);
    });
});

Best Practices

Prefer one comprehensive assertion over multiple precise ones:
// Good: Single snapshot-style assertion
test('should transform data', () => {
    const result = transform(input);
    assert.deepStrictEqual(result, {
        id: 1,
        name: 'test',
        values: [1, 2, 3]
    });
});

// Avoid: Multiple assertions
test('should transform data', () => {
    const result = transform(input);
    assert.strictEqual(result.id, 1);
    assert.strictEqual(result.name, 'test');
    assert.strictEqual(result.values.length, 3);
});
This makes tests easier to understand and update when requirements change.
Always add tests to existing suites instead of creating new files:
// Find the appropriate suite
suite('Editor Model - Existing Suite', () => {
    // Add your test here
    test('new test case', () => {
        // ...
    });
});
Don’t create a new test file unless testing a new component.
For asynchronous tests:
test('should load file asynchronously', async () => {
    const content = await fileService.readFile(uri);
    assert.ok(content.value.length > 0);
});
Don’t just test the happy path:
test('should throw on invalid input', () => {
    assert.throws(() => {
        processInput(null);
    }, /Invalid input/);
});

test('should handle async errors', async () => {
    await assert.rejects(
        async () => await loadFile('nonexistent'),
        /File not found/
    );
});
Always dispose of resources:
test('should use service', () => {
    const service = new MyService();
    try {
        // Test code
        assert.ok(service.doSomething());
    } finally {
        service.dispose();
    }
});

// Or use teardown
suite('Service Tests', () => {
    let service: MyService;

    setup(() => {
        service = new MyService();
    });

    teardown(() => {
        service.dispose();
    });
});

Integration Test Example

Integration tests use the full VS Code API:
import * as vscode from 'vscode';
import * as assert from 'assert';

suite('Markdown Extension Integration Tests', () => {
    test('should provide completions', async () => {
        const doc = await vscode.workspace.openTextDocument({
            content: '# Hello\n\n',
            language: 'markdown'
        });

        const editor = await vscode.window.showTextDocument(doc);
        const position = new vscode.Position(1, 0);

        const completions = await vscode.commands.executeCommand<vscode.CompletionList>(
            'vscode.executeCompletionItemProvider',
            doc.uri,
            position
        );

        assert.ok(completions);
        assert.ok(completions.items.length > 0);
    });
});

Smoke Tests

Smoke tests verify basic functionality in a real environment:
npm run smoketest
Smoke Test Location: test/smoke/ Coverage: File operations, editor features, extensions, settings, etc.

CI/CD Test Execution

Tests run automatically in CI/CD:
npm run core-ci        # Full test suite
npm run core-ci-pr     # PR validation
Runs:
  • Unit tests
  • Integration tests
  • Code hygiene checks
  • Layer validation

Troubleshooting

  • Check Node.js version matches CI
  • Ensure clean git state: git clean -xfd
  • Rebuild: npm run compile
  • Check for OS-specific issues
  • Increase timeout in test:
    test('slow test', async function() {
        this.timeout(10000); // 10 seconds
        // ...
    });
    
  • Check for deadlocks or infinite loops
  • Run with --inspect-brk to debug
  • Ensure extension is compiled: npm run watch-extensions
  • Check workspace folder exists
  • Verify extension activation events
  • Check logs in .build/logs/integration-tests
  • Run npm install
  • Clear cache: rm -rf out node_modules/.cache
  • Recompile: npm run compile

Next Steps

Debugging

Learn how to debug tests

Development Workflow

Understand the development process

Building from Source

Review build instructions

Mocha Documentation

Learn more about Mocha testing framework