-
-
Save bfunc/e0385fa89c7dc19b0ca5ecc763276ba7 to your computer and use it in GitHub Desktop.
This is a small set of functions to help you write simple tests without frameworks
This file contains 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
type Config = { | |
logPerformance?: boolean; | |
timeout?: number; // Timeout in milliseconds | |
}; | |
function createTestRunner(initialConfig?: Config) { | |
let globalConfig: Config = { | |
logPerformance: false, | |
timeout: 5000, | |
...initialConfig, | |
}; | |
const assert = { | |
condition: (condition: boolean, message?: string) => { | |
if (!condition) { | |
throw new Error(message || "Assertion failed: condition is false"); | |
} | |
}, | |
isDeepEqual: (a: any, b: any, message?: string) => { | |
const stringify1 = JSON.stringify(a); | |
const stringify2 = JSON.stringify(b); | |
if (stringify1 !== stringify2) { | |
throw new Error( | |
message || | |
`Assertion failed: values are not equal ${stringify1} !== ${stringify2}` | |
); | |
} | |
}, | |
shouldThrow: (fn: Function) => { | |
const message = "Assertion failed: the function hasn't thrown"; | |
try { | |
fn(); | |
throw new Error(message); | |
} catch (e) { | |
if (e instanceof Error && e.message === message) { | |
throw e; | |
} | |
return true; | |
} | |
}, | |
}; | |
function setConfig(config: Config) { | |
globalConfig = { ...globalConfig, ...config }; | |
} | |
function it( | |
desc: string, | |
fn: (done: (error?: any) => void) => void | Promise<void>, | |
config?: Config | |
) { | |
const { logPerformance, timeout } = { ...globalConfig, ...config }; | |
const startTime = Date.now(); | |
const testPromise = executeTestFunction(fn, timeout); | |
handleTestResult(testPromise, desc, startTime, logPerformance); | |
} | |
function executeTestFunction(fn: Function, timeout?: number): Promise<void> { | |
return new Promise<void>((resolve, reject) => { | |
let doneCalled = false; | |
const done = (error?: any) => { | |
if (doneCalled) { | |
reject(new Error("done() called multiple times")); | |
return; | |
} | |
doneCalled = true; | |
if (error) { | |
reject(error); | |
} else { | |
resolve(); | |
} | |
}; | |
try { | |
const result = fn.length > 0 ? fn(done) : fn(); | |
if (result instanceof Promise) { | |
result.then(resolve).catch(reject); | |
} else if (fn.length === 0 && result === undefined) { | |
// Synchronous test passed | |
resolve(); | |
} | |
if (fn.length > 0 && result === undefined) { | |
const timeoutDuration = timeout ?? globalConfig.timeout ?? 5000; | |
setTimeout(() => { | |
if (!doneCalled) { | |
reject(new Error("Test timed out: done() was not called")); | |
} | |
}, timeoutDuration); | |
} | |
} catch (error) { | |
reject(error); | |
} | |
}); | |
} | |
function handleTestResult( | |
testPromise: Promise<void>, | |
desc: string, | |
startTime: number, | |
logPerformance?: boolean | |
) { | |
testPromise | |
.then(() => { | |
logTestSuccess(desc, startTime, logPerformance); | |
}) | |
.catch((error) => { | |
logTestFailure(desc, startTime, error, logPerformance); | |
}); | |
} | |
function logTestSuccess( | |
desc: string, | |
startTime: number, | |
logPerformance?: boolean | |
) { | |
const endTime = Date.now(); | |
let message = `\x1b[32m\u2714 ${desc}\x1b[0m`; | |
if (logPerformance) { | |
const duration = endTime - startTime; | |
message += ` (Duration: ${duration} ms)`; | |
} | |
console.log(message); | |
} | |
function logTestFailure( | |
desc: string, | |
startTime: number, | |
error: any, | |
logPerformance?: boolean | |
) { | |
const endTime = Date.now(); | |
let message = `\n\x1b[31m\u2718 ${desc}\x1b[0m`; | |
if (logPerformance) { | |
const duration = endTime - startTime; | |
message += ` (Duration: ${duration} ms)`; | |
} | |
console.log(message); | |
console.error(error); | |
} | |
// Return the methods | |
return { it, assert, setConfig }; | |
} | |
// Usage example | |
const { it, assert, setConfig } = createTestRunner({ | |
logPerformance: true, | |
timeout: 5000, | |
}); | |
// Synchronous test | |
it("should add numbers correctly", () => { | |
const result = 1 + 1; | |
assert.condition(result === 2, "1 + 1 should equal 2"); | |
}); | |
// Promise-based asynchronous test | |
it("should resolve after 1 second", () => { | |
return new Promise<void>((resolve) => { | |
setTimeout(() => { | |
assert.condition(true); | |
resolve(); | |
}, 1000); | |
}); | |
}); | |
// Callback-based asynchronous test with custom timeout | |
it( | |
"should call done after async operation", | |
(done) => { | |
setTimeout(() => { | |
assert.condition(true); | |
done(); | |
}, 3000); | |
}, | |
{ | |
timeout: 4000, | |
} | |
); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment