move to vitest (#18)

* feat: move to vitest

* fix: lock file

* fix: tests running sequentially on same port

* fix lock file

---------

Co-authored-by: Paul Carleton <paulcarletonjr@gmail.com>
This commit is contained in:
Matt 2025-11-13 13:32:07 +00:00 • committed by GitHub
commit aeceefa9cf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 2069 additions and 4025 deletions

View file

@ -1,8 +0,0 @@
export default {
preset: 'ts-jest/presets/default-esm',
extensionsToTreatAsEsm: ['.ts'],
testEnvironment: 'node',
moduleNameMapper: {
'^(\\.{1,2}/.*)\\.js$': '$1'
}
};

5067
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -12,7 +12,8 @@
}, },
"scripts": { "scripts": {
"start": "tsx src/index.ts", "start": "tsx src/index.ts",
"test": "jest", "test": "vitest run",
"test:watch": "vitest",
"build": "tsdown src/index.ts --minify --clean --target node20", "build": "tsdown src/index.ts --minify --clean --target node20",
"lint": "eslint src/ examples/ && prettier --check .", "lint": "eslint src/ examples/ && prettier --check .",
"lint:fix": "eslint src/ examples/ --fix && prettier --write .", "lint:fix": "eslint src/ examples/ --fix && prettier --write .",
@ -29,19 +30,17 @@
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.8.0", "@eslint/js": "^9.8.0",
"@jest/globals": "^29.7.0",
"@types/express": "^5.0.3", "@types/express": "^5.0.3",
"@types/jest": "^29.5.8", "@types/node": "^22.10.2",
"@typescript/native-preview": "^7.0.0-dev.20251111.1", "@typescript/native-preview": "^7.0.0-dev.20251111.1",
"eslint": "^9.8.0", "eslint": "^9.8.0",
"eslint-config-prettier": "^10.1.8", "eslint-config-prettier": "^10.1.8",
"jest": "^29.7.0",
"prettier": "3.6.2", "prettier": "3.6.2",
"ts-jest": "^29.1.1",
"tsdown": "^0.16.2", "tsdown": "^0.16.2",
"tsx": "^4.7.0", "tsx": "^4.7.0",
"typescript": "^5.5.4", "typescript": "^5.5.4",
"typescript-eslint": "^8.0.0" "typescript-eslint": "^8.0.0",
"vitest": "^2.1.8"
}, },
"dependencies": { "dependencies": {
"@modelcontextprotocol/sdk": "^1.20.1", "@modelcontextprotocol/sdk": "^1.20.1",

View file

@ -1,4 +1,3 @@
import { describe, test } from '@jest/globals';
import { import {
runClientAgainstScenario, runClientAgainstScenario,
SpawnedClientRunner SpawnedClientRunner

View file

@ -1,4 +1,3 @@
import { describe, test } from '@jest/globals';
import { import {
runClientAgainstScenario, runClientAgainstScenario,
SpawnedClientRunner SpawnedClientRunner

View file

@ -3,13 +3,13 @@ import { getClientScenario, listActiveClientScenarios } from '../index.js';
import path from 'path'; import path from 'path';
describe('Server Scenarios', () => { describe('Server Scenarios', () => {
let serverProcess: ChildProcess; let serverProcess: ChildProcess | null = null;
const TEST_PORT = 3001; const TEST_PORT = 3001;
const SERVER_URL = `http://localhost:${TEST_PORT}/mcp`; const SERVER_URL = `http://localhost:${TEST_PORT}/mcp`;
const SERVER_STARTUP_TIMEOUT = 10000; // 10 seconds to start const SERVER_STARTUP_TIMEOUT = 30000; // 30 seconds for CI
beforeAll(async () => { beforeAll(async () => {
// Start the everything-server once for all scenarios // Start the everything-server once for all scenarios in this file
const serverPath = path.join( const serverPath = path.join(
process.cwd(), process.cwd(),
'examples/servers/typescript/everything-server.ts' 'examples/servers/typescript/everything-server.ts'
@ -17,61 +17,85 @@ describe('Server Scenarios', () => {
serverProcess = spawn('npx', ['tsx', serverPath], { serverProcess = spawn('npx', ['tsx', serverPath], {
env: { ...process.env, PORT: TEST_PORT.toString() }, env: { ...process.env, PORT: TEST_PORT.toString() },
stdio: ['ignore', 'pipe', 'pipe'] stdio: ['ignore', 'pipe', 'pipe'],
detached: false
});
// Capture output for debugging
let stdoutData = '';
let stderrData = '';
serverProcess.stdout?.on('data', (data) => {
stdoutData += data.toString();
});
serverProcess.stderr?.on('data', (data) => {
stderrData += data.toString();
}); });
// Wait for server to be ready // Wait for server to be ready
await new Promise<void>((resolve, reject) => { await new Promise<void>((resolve, reject) => {
const timeout = setTimeout(() => { const timeout = setTimeout(() => {
if (serverProcess) {
serverProcess.kill('SIGKILL');
}
reject( reject(
new Error(`Server failed to start within ${SERVER_STARTUP_TIMEOUT}ms`) new Error(`Server failed to start within ${SERVER_STARTUP_TIMEOUT}ms`)
); );
}, SERVER_STARTUP_TIMEOUT); }, SERVER_STARTUP_TIMEOUT);
serverProcess.stdout?.on('data', (data) => { let resolved = false;
serverProcess!.stdout?.on('data', (data) => {
const output = data.toString(); const output = data.toString();
if (output.includes('running on')) { if (output.includes('running on')) {
clearTimeout(timeout); clearTimeout(timeout);
resolved = true;
resolve(); resolve();
} }
}); });
serverProcess.on('error', (error) => { serverProcess!.on('error', (error) => {
clearTimeout(timeout); clearTimeout(timeout);
reject(new Error(`Failed to start server: ${error.message}`)); reject(new Error(`Failed to start server: ${error.message}`));
}); });
serverProcess.on('exit', (code) => { serverProcess!.on('exit', (code) => {
if (code !== null && code !== 0) { // Only reject if server exits unexpectedly during startup
if (!resolved && code !== null && code !== 0) {
clearTimeout(timeout); clearTimeout(timeout);
reject(new Error(`Server exited prematurely with code ${code}`)); reject(
new Error(
`Server exited prematurely with code ${code}. STDOUT: ${stdoutData}, STDERR: ${stderrData}`
)
);
} }
}); });
}); });
}, SERVER_STARTUP_TIMEOUT + 5000); }, SERVER_STARTUP_TIMEOUT + 5000);
afterAll(async () => { afterAll(async () => {
// Stop the server // Stop the server and clean up
if (serverProcess) { if (serverProcess && !serverProcess.killed) {
// Try graceful shutdown first
serverProcess.kill('SIGTERM'); serverProcess.kill('SIGTERM');
// Wait for graceful shutdown // Wait for graceful shutdown with timeout
await new Promise<void>((resolve) => { await new Promise<void>((resolve) => {
const timeoutHandle = setTimeout(() => { const killTimeout = setTimeout(() => {
if (!serverProcess.killed) { if (serverProcess && !serverProcess.killed) {
serverProcess.kill('SIGKILL'); serverProcess.kill('SIGKILL');
} }
resolve(); resolve();
}, 5000); }, 5000);
const cleanUp = () => { serverProcess!.once('exit', () => {
clearTimeout(timeoutHandle); clearTimeout(killTimeout);
serverProcess.removeListener('exit', cleanUp);
resolve(); resolve();
}; });
serverProcess.on('exit', cleanUp);
}); });
serverProcess = null;
} }
}); });

View file

@ -7,7 +7,8 @@
"strict": true, "strict": true,
"esModuleInterop": true, "esModuleInterop": true,
"skipLibCheck": true, "skipLibCheck": true,
"forceConsistentCasingInFileNames": true "forceConsistentCasingInFileNames": true,
"types": ["node", "vitest/globals"]
}, },
"include": ["src/**/*", "examples/clients/typescript/**/*"], "include": ["src/**/*", "examples/clients/typescript/**/*"],
"exclude": ["node_modules"] "exclude": ["node_modules"]

15
vitest.config.ts Normal file
View file

@ -0,0 +1,15 @@
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true,
environment: 'node',
include: ['**/*.test.ts'],
exclude: ['node_modules', 'dist'],
// Run test files sequentially to avoid port conflicts
fileParallelism: false,
// Increase timeout for server tests in CI
testTimeout: 15000,
hookTimeout: 30000
}
});