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

View file

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

View file

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

View file

@ -3,13 +3,13 @@ import { getClientScenario, listActiveClientScenarios } from '../index.js';
import path from 'path';
describe('Server Scenarios', () => {
let serverProcess: ChildProcess;
let serverProcess: ChildProcess | null = null;
const TEST_PORT = 3001;
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 () => {
// Start the everything-server once for all scenarios
// Start the everything-server once for all scenarios in this file
const serverPath = path.join(
process.cwd(),
'examples/servers/typescript/everything-server.ts'
@ -17,61 +17,85 @@ describe('Server Scenarios', () => {
serverProcess = spawn('npx', ['tsx', serverPath], {
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
await new Promise<void>((resolve, reject) => {
const timeout = setTimeout(() => {
if (serverProcess) {
serverProcess.kill('SIGKILL');
}
reject(
new Error(`Server failed to start within ${SERVER_STARTUP_TIMEOUT}ms`)
);
}, SERVER_STARTUP_TIMEOUT);
serverProcess.stdout?.on('data', (data) => {
let resolved = false;
serverProcess!.stdout?.on('data', (data) => {
const output = data.toString();
if (output.includes('running on')) {
clearTimeout(timeout);
resolved = true;
resolve();
}
});
serverProcess.on('error', (error) => {
serverProcess!.on('error', (error) => {
clearTimeout(timeout);
reject(new Error(`Failed to start server: ${error.message}`));
});
serverProcess.on('exit', (code) => {
if (code !== null && code !== 0) {
serverProcess!.on('exit', (code) => {
// Only reject if server exits unexpectedly during startup
if (!resolved && code !== null && code !== 0) {
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);
afterAll(async () => {
// Stop the server
if (serverProcess) {
// Stop the server and clean up
if (serverProcess && !serverProcess.killed) {
// Try graceful shutdown first
serverProcess.kill('SIGTERM');
// Wait for graceful shutdown
// Wait for graceful shutdown with timeout
await new Promise<void>((resolve) => {
const timeoutHandle = setTimeout(() => {
if (!serverProcess.killed) {
const killTimeout = setTimeout(() => {
if (serverProcess && !serverProcess.killed) {
serverProcess.kill('SIGKILL');
}
resolve();
}, 5000);
const cleanUp = () => {
clearTimeout(timeoutHandle);
serverProcess.removeListener('exit', cleanUp);
serverProcess!.once('exit', () => {
clearTimeout(killTimeout);
resolve();
};
serverProcess.on('exit', cleanUp);
});
});
serverProcess = null;
}
});

View file

@ -7,7 +7,8 @@
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
"forceConsistentCasingInFileNames": true,
"types": ["node", "vitest/globals"]
},
"include": ["src/**/*", "examples/clients/typescript/**/*"],
"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
}
});