Why is a mocked jest promise not rejected with allSettled? - javascript

I want to test a method which returns the result of Promise.allSettled() and calls another function which returns promises.
I reduced the problem to the following test code:
describe('Promise tests', () => {
it('should reject directly', async () => {
const f = jest.fn().mockRejectedValue(new Error('foo'));
const p = async () => await f();
// works
await expect(p).rejects.toThrow('foo');
});
it('should reject with allSettled', async () => {
const f = jest.fn().mockRejectedValue(new Error('foo'));
const p = async () => await f();
const results = await Promise.allSettled([p]);
expect(results[0].status).toBe('rejected'); // fulfilled - but why?
expect(results[0].reason).toBe('foo');
});
});
Why is the second case not receiving a rejected promise?
node.js v14.3.0
jest v25.4.0

You are almost there. Promise.allSettled expects to receive an array of Promises not an array of functions returning a promise, which in fact is what your constant p does.
By simply calling the p() you solve your issue:
describe('Promise tests', () => {
it('should reject directly', async () => {
const f = jest.fn().mockRejectedValue(new Error('foo'));
const p = async () => await f();
// works
await expect(p()).rejects.toThrow('foo');
});
it('should reject with allSettled', async () => {
const f = jest.fn().mockRejectedValue(new Error('foo'));
const p = async () => await f();
const results = await Promise.allSettled([p()]);
expect(results[0].status).toBe('rejected'); // fulfilled - but why?
expect(results[0].reason).toBe('foo');
});
});
By the way: My linter complains about unnecessary awaits :-)

Related

Create a jest test to fail an async function which does not use await

async function isContentUser (userEmail, studentStatus) {
const hasContentAccess = getContentCreatorAccess()
const hasContentStudentStatus = studentStatus === CONTENT
return hasContentStudentStatus || hasContentAccess
}
async function getContentCreatorAccess () {
const { contentCreatorAccess } = await api.getContentCreatorPermission()
return contentCreatorAccess
}
As you can see in isContentUser function, we're not awaiting getContentCreateAccess, but the below existing test pass, because hasContentAccess in the return value is a promise and we're awaiting the promise in the test.
And I want to create a PR which adds await before getContentCreatorAccess call, how can I come up with a test which works with await but not in the current code.
it(`should return true if studentStatus is not Content but user has content permission`, async () => {
api.getContentCreatorPermission.mockResolvedValue({
contentCreatorAccess: true
})
expect(await isContentUser('email#gmail.com', 'Audit')).toBe(true)
})

Sinon useFakeTimers mocking delay promise times out

I implemented a non-blocking delay using Promise and setTimeout as follows:
await new Promise((resolve) => setTimeout(resolve, 200))
I have a function which calls this delay:
const fn1 = async (v) => {
console.log(v)
}
const fn2 = async () => {
await fn1(1111111)
await new Promise((resolve) => setTimeout(resolve, 200))
await fn1(2222222)
}
I then tried to use useFakeTimers from Sinon to mock the delay in a unit test (Mocha):
const sinon = require('sinon')
describe('Test 1', () => {
it('Test 1.1', async () => {
const sb = sinon.createSandbox()
sb.useFakeTimers()
await fn2()
sb.clock.tick(500)
expect(1).to.equal(1)
sb.clock.restore()
})
})
I expected sb.clock.tick(500) to mock time passing for 500ms such that the delay promise (await new Promise((resolve) => setTimeout(resolve, 200))) would resolve, 2222222 would be printed, and the test would pass.
However, I got the following error:
Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.
I'm not sure if my understanding of useFakeTimers is wrong...
It seems the correct solution for this issue is to use clock.tickAsync.
I updated the target function a little bit for it to be more testable:
const fn = async (callback) => {
await callback(1111111)
await new Promise((resolve) => setTimeout(resolve, 200))
await callback(2222222)
}
And the working test is as follows:
describe('Test 1', () => {
let sb
beforeEach(() => {
sb = sinon.createSandbox()
sb.useFakeTimers()
})
afterEach(() => {
sb.clock.restore()
})
it('Test 1.1', async () => {
const callbackSpy = sinon.spy()
fn(callbackSpy)
expect(callbackSpy.callCount).to.equal(1)
await sb.clock.tickAsync(210)
expect(callbackSpy.callCount).to.equal(2)
expect(callbackSpy.calledWith(1111111)).to.equal(true)
expect(callbackSpy.calledWith(2222222)).to.equal(true)
})
})
And problem is that clock.tickAsync is only available in later versions of sinon.
For some earlier versions (e.g. 2.4), clock.tickAsync doesn't exist and I haven't figured out how to test the case above.

Which is better Promise.all or nested async await?

I have two blocks of code. First is using async await
async sendEmailNotifications() {
try {
const users = await User.find(...)
const promises = users.map(async(user) => {
const _promises = user.appId.map(async(app) => {
const todayVisitorsCount = await Session.count({...})
const yesterdayVisitorsCount = await UserSession.count({...})
const emailObj = {
todayVisitorsCount,
yesterdayVisitorsCount
}
const sendNotification = await emailService.analyticsNotification(emailObj)
})
await Promise.all(_promises)
})
return promises
} catch (err) {
return err
}
}
(await sendEmailNotifications())
And then I have using Promise.all
sendEmailNotifications() {
const users = await User.find(...)
const promises = users.map((user) => {
const allPromises = []
user.appId.map((app) => {
allPromises.push(UserSession.count({...}))
allPromises.push(Session.count({...}))
})
const data = await Promise.all(allPromises)
const emailObj = {
todayVisitorsCount: data[0],
yesterdayVisitorsCount: data[1]
}
const sendNotification = await emailService.analyticsNotification(emailObj)
})
return promises
}
sendNotification.then((data) => console.log(data))
Now I need to know which piece of code will faster execute? One is with series(async await) and one is with parellel(Promise.all). Which has better performance?
In the first code, you have two separate await statements:
const todayVisitorsCount = await Session.count({...})
const yesterdayVisitorsCount = await UserSession.count({...})
whereas in the second, you only have one, before a Promise.all:
const data = await Promise.all(allPromises)
In the first code, the second Promise will only initialize after the first Promise has finished, resulting in a longer time required before the script ends. For example:
const fn = () => new Promise(resolve => setTimeout(resolve, 1000));
console.log('start');
(async () => {
await fn();
await fn();
console.log('two awaits done');
})();
(async () => {
await Promise.all([fn(), fn()]);
console.log('Promise.all done');
})();
The version without Promise.all pauses the function when the first call of fn() is made, and waits for the Promise returned by fn() to resolve (1000 ms) before proceeding to the next line. The next line calls fn() again, and the await waits for it to complete (1000 more ms).
In contrast, the Promise.all version calls both fn()s immediately - both Promises are initialized, and the await that pauses the function is waiting for both Promises to complete. There's no down time between the initialization of the first Promise and the initialization of the second Promise.
So, the Promise.all version will run more significantly more quickly than the version with two awaits. Using Promise.all will be preferable unless the first Promise (UserSession.count) must be completed before the second Promise (Session.count) starts.
With destructuring and without unnecessary variables, this is how I would clean up your Promise.all code, you might consider it to be a bit more readable:
async sendEmailNotifications() {
const users = await User.find();
return users.map(async (user) => {
const [todayVisitorsCount, yesterdayVisitorsCount] = await Promise.all([
UserSession.count(),
Session.count()
]);
await emailService.analyticsNotification({ todayVisitorsCount, yesterdayVisitorsCount });
});
}

Test failing after adding then and catch to promise

I have the following (simplified for the sake of scoping down problem) code:
function pushPromises() {
const promises = [];
promises.push(firstPromise('something'))
promises.push(secondPromise('somethingelse'))
return promises;
}
export default handlePromises(async (c) => {
const results = await Promise.all(pushPromises())
c.success(results);
});
My test mocks those firstPromise and secondPromise to check if they were called with the right arguments. This works (assume mock set up is properly done):
jest.mock('src/firstPromise');
jest.mock('src/secondPromise');
describe('test', () => {
let firstMock;
let secondMock;
beforeEach(() => {
require('src/firstPromise').default = firstMock;
require('src/secondPromise').default = secondMock;
})
it('test', async () => {
await handlePromises(context);
expect(firstPromiseMock).toHaveBeenCalledTimes(1);
expect(secondPromiseMock).toHaveBeenCalledTimes(1);
});
});
Now, if I add handlers to the promises such as:
function pushPromises() {
const promises = [];
promises.push(
firstPromise('something')
.then(r => console.log(r))
.catch(e => console.log(e))
)
promises.push(
secondPromise('somethingelse')
.then(r => console.log(r))
.catch(e => console.log(e))
)
return promises;
}
The first expectation passes, but the second one doesn't. It looks like the second promise is no longer called.
How can I modify the test/code so that adding handlers on the promises don't make the test break? It looks like it is just finishing the execution on the first promise handler and does never get to the second one.
EDIT:
I have modified the function to return a Promise.all without await:
export default handlePromises(async (c) => {
return Promise.all(pushPromises())
});
But I'm having the exact same issue. The second promise is not called if the first one has a .then.
In your edit, your handlePromises is still not a promise..
Try the following. ->
it('test', async () => {
await Promise.all(pushPromises());
expect(firstPromiseMock).toHaveBeenCalledTimes(1);
expect(secondPromiseMock).toHaveBeenCalledTimes(1);
});
Your handlePromises function accepts a callback but you are handling it as it returns a promise, it is not a good way to go but you can test your method using callback as following
Modify your Test as=>
it('test', (done) => {
handlePromises(context);
context.success = (results) => {
console.log(results)
expect(firstPromiseMock).toHaveBeenCalledTimes(1);
expect(secondPromiseMock).toHaveBeenCalledTimes(1);
done();
}
});
Or modify your code as =>
function pushPromises() {
const promises = [];
promises.push(firstPromise('something'))
promises.push(secondPromise('somethingelse'))
return promises;
}
export default handlePromises = (context) => {
return Promise.all(pushPromises()).then((data) => context.success(data))
};
//with async
export default handlePromises = async (context) => {
let data = await Promise.all(pushPromises());
context.success(data)
};

Wait for async function in multiple places

With Promises I can have two separate "threads" both waiting for the same value:
let trigger;
const promise = new Promise(r => {
console.log('promise is created *once*');
trigger = value => {
console.log('trigger is called *once*');
r(value);
}
});
(async () => {
console.log('A waiting');
const value = await promise;
console.log(`A finished, got ${value}`);
})();
(async () => {
console.log('B waiting');
const value = await promise;
console.log(`B finished, got ${value}`);
})();
trigger('hello');
console.log('And *two* things are waiting on the single promise');
I've tried to replicate this with async/await but to no avail.
The following snippet does not work:
let trigger = async () => {
console.log('trigger should be called *once*');
return 'hello';
};
(async () => {
console.log('A waiting');
const value = await trigger; // <-- What do I need to put here?
console.log(`A finished, got ${value}`);
})();
(async () => {
console.log('B waiting');
const value = await trigger; // <-- What do I need to put here?
console.log(`B finished, got ${value}`);
})();
trigger(); // <-- How can this "kick off" the two awaits above?
Is it possible to write the same functionality in the first snippet, using async/await?
I'm okay with falling back to using Promise if that's needed.
To await, you need to have a reference to the singular promise, so you can't call a function on demand and have that function create a promise and then use that same promise elsewhere (unless the function that creates the promise also holds it in state to return to other callers, like a singleton).
I'd create a singular promise initially, and then send it to the async functions:
const trigger = async () => {
console.log('trigger should be called *once*');
return 'hello';
};
async function as1(prom) {
console.log('A waiting');
const value = await prom;
console.log(`A finished, got ${value}`);
}
async function as2(prom) {
console.log('B waiting');
const value = await prom;
console.log(`B finished, got ${value}`);
}
const thePromise = trigger();
as1(thePromise);
as2(thePromise);
Don't use async just to return a promise, though - if the purpose of a function is to create a promise, then do it explicitly - that way, it's more clear what your code is meant to do. Async and await have not made the Promise keyword obsolete, it's just syntax sugar which is useful in some situations (and unnecessary in others).

Categories

Resources