Created
November 9, 2025 17:13
-
-
Save nikoheikkila/bb31c6741e2abf980a5a1984d4c2faff to your computer and use it in GitHub Desktop.
TypeScript & Vitest performance test
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { afterAll, beforeAll, describe, expect, it } from "vitest"; | |
| import https from "node:https"; | |
| interface Result { | |
| success: boolean; | |
| statusCode?: number; | |
| error?: string; | |
| } | |
| /** | |
| * Simulates load testing using a simple Vitest test suite and Node.js's | |
| * built-in HTTPS module. | |
| */ | |
| describe("HTTP Load Test", () => { | |
| const maxRequests = 1000; | |
| const requestTimeout = 30_000; | |
| const timeout = 60_000; | |
| const targetUrl = new URL(process.env.URL ?? ""); | |
| let agent: https.Agent; | |
| beforeAll(() => { | |
| /** | |
| * By default, Node.js creates a new TCP connection for every request, | |
| * which includes DNS lookup and TCP handshake overhead. This is extremely | |
| * inefficient for load testing where we're hitting the same endpoint repeatedly. | |
| * | |
| * With keep-alive enabled, connections are reused, which can improve throughput | |
| * and significantly reduce CPU usage. | |
| */ | |
| agent = new https.Agent({ | |
| keepAlive: true, | |
| keepAliveMsecs: 3000, | |
| maxSockets: maxRequests, | |
| maxFreeSockets: 256, | |
| timeout, | |
| }); | |
| }); | |
| afterAll(() => { | |
| agent.destroy(); | |
| }); | |
| it( | |
| `with ${maxRequests} requests`, | |
| async () => { | |
| const results = { | |
| success: 0, | |
| failures: [] as string[], | |
| }; | |
| const requests = Array.from({ length: maxRequests }, () => makeRequest(targetUrl)); | |
| const responses = await Promise.all(requests); | |
| responses.forEach((response) => { | |
| if (response.error) { | |
| results.failures.push(response.error); | |
| return; | |
| } | |
| if (response.success) { | |
| results.success++; | |
| return; | |
| } | |
| throw new Error(`Unexpected response: ${JSON.stringify(response)}`); | |
| }); | |
| console.log("\n=== Load Test Results ==="); | |
| console.log(`Total requests: ${maxRequests}`); | |
| console.log(`Successful: ${results.success}`); | |
| console.log(`Failed: ${results.failures.length}`); | |
| if (results.failures.length > 0) { | |
| const errorCounts = results.failures.reduce( | |
| (total, error) => { | |
| total[error] = (total[error] || 0) + 1; | |
| return total; | |
| }, | |
| {} as Record<string, number>, | |
| ); | |
| console.log("\n=== Error Summary ==="); | |
| Object.entries(errorCounts).forEach(([error, count]) => { | |
| console.log(`${error}: ${count}`); | |
| }); | |
| } | |
| const successRate = (results.success / maxRequests) * 100; | |
| expect(successRate).toBeGreaterThanOrEqual(95); | |
| }, | |
| timeout, | |
| ); | |
| const makeRequest = (url: URL): Promise<Result> => { | |
| return new Promise((resolve) => { | |
| const options = { | |
| agent, | |
| hostname: url.hostname, | |
| port: url.port || 443, | |
| path: url.pathname + url.search, | |
| method: "GET", | |
| }; | |
| const request = https.request(options, (response) => { | |
| // We discard the data since we're only testing throughput | |
| response.on("data", () => {}); | |
| response.on("end", () => { | |
| return resolve({ | |
| success: response.statusCode !== undefined && response.statusCode >= 200 && response.statusCode < 300, | |
| statusCode: response.statusCode, | |
| }); | |
| }); | |
| }); | |
| request.on("error", (error) => { | |
| return resolve({ | |
| success: false, | |
| error: error.message, | |
| }); | |
| }); | |
| // Set a timeout for the entire request | |
| request.setTimeout(requestTimeout, () => { | |
| request.destroy(); | |
| return resolve({ | |
| success: false, | |
| error: "Request timeout", | |
| }); | |
| }); | |
| return request.end(); | |
| }); | |
| }; | |
| }); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment