Skip to content

Instantly share code, notes, and snippets.

@pcafstockf
Created February 19, 2022 23:37
Show Gist options
  • Save pcafstockf/6458879d2d155c44a390e8a84fba2a4d to your computer and use it in GitHub Desktop.
Save pcafstockf/6458879d2d155c44a390e8a84fba2a4d to your computer and use it in GitHub Desktop.
Do you really need Jest?
/**
* If you wish to break free from the embrace->extend->imprison paradigm of Jest, this little Adapter can help you gradually break free.
* Use these methods along with the generic statements like xdescribe instead of describe.skip ('it', 'fit', 'xit', instead of 'test')
* Create your new tests using these adapters, and over time swap out your Jest specific calls in existing tests.
* Once your project is fully converted, you can simply replace Jasmine for Jest inside your package.json and all your tests should still be green.
*
* Jest+Yarn+jsdom expose your project to almost 4,000 currently open issues, and 400ish open PRs (somebody spent personal time to submit a fix, but it is still being ignored).
* For Node.js based applications, I personally just don't see the need for Jest.
*
* On a related note if you add CRA into the mix, you get an *additional* 2,000ish open issues and *another* 400ish open PRs.
* If the purpose of testing is to ensure stability, and if your UI tests really do take an excessive amount of time using the same environment your users actually use,
* perhaps a better strategy would be to set up a few machines each configured to run a portion of your test files.
*
* FUTURE:
* I may add Mocha in here, and am open to suggestions for other helpful cross-environment adapter functions.
* All my projects are TypeScript, but if there is any real interest here, I'll update this gist with a pure JavaScript version.
*
* FAIR WARNING!
* I've been using this code for a couple of years.
* However, when I decided to post this gist, I added the 3 "tick" functions at the bottom.
* I have never used the tick functions and don't actually know if they work :-)
* Furthermore, they require at least Jest 26 to work.
*
* This is free and unencumbered software released into the public domain to be used however you wish.
* Specifically it is released under "The Unlicense" <http://unlicense.org/> as defined on 2022/02/19
*/
declare var jest;
/**
* Set the timeout value for when a "hung" test is considered to have failed
*/
export function setTestTimeout(ms: number) {
if (typeof jest !== 'undefined') {
if (typeof ms === 'number')
jest.setTimeout(ms);
}
else {
let originalTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL;
beforeAll(function() {
jasmine.DEFAULT_TIMEOUT_INTERVAL = ms;
});
afterAll(function() {
jasmine.DEFAULT_TIMEOUT_INTERVAL = originalTimeout;
});
}
}
/**
* Create a simple function that when invoked will record its arguments and return value for each invocation.
*
* @param impl An optional delegate/callback to perform user specified operations.
*/
export function createFnSpy(impl?: (...args: any[]) => any) {
if (typeof jest !== 'undefined')
return jest.fn(impl);
else {
let retVal = jasmine.createSpy('spy', impl);
if (impl) {
retVal = retVal.and.callFake((...args: any[]) => {
return impl(...args);
});
}
return retVal;
}
}
/**
* Wrap the specified method of an object and return a spy for that object.
*/
export function spyOnObject(obj: any, methodName: string) {
if (typeof jest !== 'undefined')
return jest.spyOn(obj, methodName);
else
return spyOn(obj, methodName).and.callThrough();
}
/**
* Retrieve the arguments for all (or a specific) invocation(s) of a spy function or object method
*/
export function spyCallArguments(spy: any, invocationIdx?: number) {
if (typeof jest !== 'undefined') {
if (typeof invocationIdx === 'number' && invocationIdx >= 0)
return spy.mock?.calls?.[invocationIdx];
return spy.mock?.calls;
}
else {
if (typeof invocationIdx === 'number' && invocationIdx >= 0)
return spy.calls?.argsFor(invocationIdx);
return spy.calls?.allArgs();
}
}
/**
* Retrieve the results of all (or a specific) invocation(s) of a spy function or object method.
*/
export function spyCallResult(spy: any, invocationIdx?: number) {
if (typeof jest !== 'undefined') {
if (typeof invocationIdx === 'number' && invocationIdx >= 0)
spy.mock?.results?.[invocationIdx]?.value;
return spy.mock?.results?.map(m => m.value);
}
else {
if (typeof invocationIdx === 'number' && invocationIdx >= 0)
return spy.calls?.all()[invocationIdx]?.returnValue;
return spy.calls?.all().map(m => m.returnValue);
}
}
/**
* Exit a test with a specific failure.
*/
export function failTest(err: string | Error, done?: (e?: Error) => void) {
if (typeof jest !== 'undefined') {
if (typeof err === 'string')
err = new Error(err);
if (typeof done === 'function')
done(err);
else
throw err;
}
else {
fail(err);
if (typeof done === 'function')
done();
}
}
/**
* Stop the system clock from automatically advancing.
* WARNING: Don't forget to ensure @see resumeTicks is called to undo the effects of this function.
* @param date Optionally set the system time to a specific Date.
*/
export function suspendTicks(date?: Date) {
if (typeof jest !== 'undefined') {
jest.useFakeTimers();
if (date)
jest.setSystemTime(date.getTime());
}
else {
jasmine.clock().install();
if (date)
jasmine.clock().mockDate(date);
}
}
/**
* Advance the system clock by the specified number of milliseconds.
*/
export function advanceTicks(ms: number) {
if (typeof jest !== 'undefined') {
jest.advanceTimersByTime(ms);
}
else {
jasmine.clock().tick(ms);
}
}
/**
* Undo the effects of @see suspendTicks
*/
export function resumeTicks() {
if (typeof jest !== 'undefined') {
jest.useRealTimers();
}
else {
jasmine.clock().uninstall();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment