- It is not possible to reassign an argument in a method.
(a) => { a = 2; }
will not work.
However, it is possible to reassign a property of an argument object in a method, so(a) => { a.value = 2; }
will work. - Promise execution has a different queue than normal code. This can be important in testing async code: in the test we'll have to make sure the test execution waits for the promise to resolve, before testing the return value or side effect.
- Optional chaining saves you the trouble of testing for undefined keys in nested objects. Eg.
person.address?.street?.name
- Use a variable's value as a key in and object with this syntax:
{ [yourKeyVariable]: someValue }
- ...
Tuesday, April 27, 2021
Javascript Tips and Tricks
Testing a function with setTimeout and promises in Jest in Javascript
Inspiration for the code taken from Jest timer mocks documentation.
Given a function that has both async/await and timeout, to test it with Jest, we need to know the following:
- use
jest.useFakeTimers()
to get control of the timing - for each timeout, use the Jest Timer Control that applies
jest.runAllTimers()
-- Fast-forward until all timers have been executedjest.runOnlyPendingTimers()
-- Fast forward and exhaust only currently pending timers (but not any new timers that get created during that process)jest.advanceTimersByTime(1000)
-- Fast-forward the given amount of milliseconds- for each
await
in the tested function, addawait Promise.resolve()
in the test, or any other way to resolve the promise before the test execution continues
The function to be tested:
async function infiniteAsyncTimerGame (beginningCallback, endingCallback) {
console.log('start round');
beginningCallback && await beginningCallback();
console.log('awaited beginningCallback, will now set timeout');
setTimeout(async () => {
console.log('continuing after timeout');
endingCallback && await endingCallback();
console.log('awaited endingCallback, will now call function again');
infiniteAsyncTimerGame(beginningCallback, endingCallback);
}, 10000);
console.log('end round');
}
The test:test('infiniteAsyncTimerGame', async () => {
// Preparations
jest.useFakeTimers();
let beginCounter = 0;
let endCounter = 0;
async function beginningCallback () {
await new Promise(resolve => resolve(++beginCounter));
}
async function endingCallback () {
await new Promise(resolve => resolve(++endCounter));
}
// Begin testing
// notice the await keyword in front of the method call
await infiniteAsyncTimerGame(beginningCallback, endingCallback);
// At this point in time, there should have been a call to beginningCallback
// We need to trigger the processing of each `await` keyword in the tested code
// with `await Promise.resolve()` in the test run to flush the Promise queue
// after that we can check for the value the Promise returned
await Promise.resolve();
console.log('check beginCounter');
expect(beginCounter).toEqual(1);
// After the beginningCallback, there should have been a single call to
// setTimeout to schedule the next round in 10 seconds
console.log('check timeout');
expect(setTimeout).toHaveBeenCalledTimes(1);
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 10000);
// Fast forward and exhaust only currently pending timers
// (but not any new timers that get created during that process)
console.log('runOnlyPendingTimers');
jest.runOnlyPendingTimers();
// At this point in time, there should have been a call to endingCallback
await Promise.resolve();
console.log('check endCounter');
expect(endCounter).toEqual(1);
// After the endingCallback, the next round should be started
// with a new call to beginningCallback
await Promise.resolve();
console.log('check beginCounter');
expect(beginCounter).toEqual(2);
console.log('check timeout');
expect(setTimeout).toHaveBeenCalledTimes(2);
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 10000);
console.log('runOnlyPendingTimers');
jest.runOnlyPendingTimers();
await Promise.resolve();
console.log('check endCounter');
expect(endCounter).toEqual(2);
// third round
await Promise.resolve();
console.log('check beginCounter');
expect(beginCounter).toEqual(3);
console.log('check timeout');
expect(setTimeout).toHaveBeenCalledTimes(3);
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 10000);
console.log('runOnlyPendingTimers');
jest.runOnlyPendingTimers();
await Promise.resolve();
console.log('check endCounter');
expect(endCounter).toEqual(3);
});
Monday, April 26, 2021
Testing the value of promises in Javascript without using Promise
Source: https://stackoverflow.com/a/54029876/4456532
Use case: when testing a code in Jest that already uses promises, I didn't want to use Promise resolve to peek into the promise, in order to not mess up the function execution.
My helper function:
function promiseValue(p) {
return process.binding('util').getPromiseDetails(p)[1];
}
Test for it with Jest:
test('promiseValue', () => {
expect(promiseValue(new Promise(()=>{}))).toEqual(undefined);
expect(promiseValue(Promise.resolve(2))).toEqual(2);
expect(promiseValue(Promise.reject(0))).toEqual(0);
})
Testing the state of promises in JavaScript without using Promise
Relevant posts:
- How to check if a Promise is pending [duplicate]
- How can I synchronously determine a JavaScript Promise's state?
Use case: I would like to test a code with Jest that already uses promises, so to not mess up the promise queue, I needed a way to peek into the promise without using Promise.resolve or Promise.race.
My helper function:
const PROMISE_STATUS = Object.freeze({
PENDING: 'pending',
FULFILLED: 'fulfilled',
REJECTED: 'rejected'
})
function promiseStatus(p) {
if (inspect(p).includes(PROMISE_STATUS.PENDING)) {
return PROMISE_STATUS.PENDING;
}
else if (inspect(p).includes(PROMISE_STATUS.REJECTED)) {
return PROMISE_STATUS.REJECTED;
}
else {
return PROMISE_STATUS.FULFILLED;
}
}
Test for it with Jest:
test('promiseStatus', () => {
expect(promiseStatus(new Promise(()=>{}))).toEqual(PROMISE_STATUS.PENDING);
expect(promiseStatus(Promise.resolve(2))).toEqual(PROMISE_STATUS.FULFILLED);
expect(promiseStatus(Promise.reject(0))).toEqual(PROMISE_STATUS.REJECTED);
})
Subscribe to:
Posts (Atom)