The missing part
Zoom on previous test
As mentioned earlier, the decision to use sequences was largely driven by the desire to cover most of the scheduler's APIs in this tutorial. However, there are alternative ways of achieving our goals.
One of the issues we wanted to address was the need to trigger queries asynchronously. Although this functionality is not yet built into fast-check, we could use the technique outlined in scheduling a function call. If we were to take this approach, we would update:
for (let id = 0; id !== numCalls; ++id) {
expectedAnswers.push(id);
pendingQueries.push(queued(id).then((v) => seenAnswers.push(v)));
}
into:
for (let id = 0; id !== numCalls; ++id) {
pendingQueries.push(
s
.schedule(Promise.resolve(`Fire the call for ${id}`))
.then(() => {
expectedAnswers.push(id);
return queued(id);
})
.then((v) => seenAnswers.push(v)),
);
}
Contrary to the batch approach, the ordering of ids will not be ensured. For that reason, we decided to include it in the reports by scheduling a resolved promise with a value featuring this id.
Towards next test
Our tests may be incomplete because we are not taking into account all aspects of the specification:
Its purpose is to wrap an asynchronous function and queue subsequent calls to it in two ways:
- Promises returned by the function will resolve in order, with the first call resolving before the second one, the second one resolving before the third one, and so on.
- Concurrent calls are not allowed, meaning that a call will always wait for the previously started one to finish before being fired.
Although we thoroughly tested the first point, we may have overlooked the second point in the specification. Therefore, in the final section of this tutorial, we will focus on validating the second requirement.
Your turn!
import {queue} from './queue.js'; import fc from 'fast-check'; test('should resolve in call order', async () => { await fc.assert(fc.asyncProperty(fc.scheduler(), fc.array(fc.integer({min: 1, max: 10}), {minLength: 1}), async (s, batches) => { // Arrange const pendingQueries = []; const seenAnswers = []; const expectedAnswers = []; const call = jest.fn() .mockImplementation(v => Promise.resolve(v)); // Act const queued = queue(s.scheduleFunction(call)); let lastId = 0; const { task } = s.scheduleSequence(batches.map((batch, index) => { return { label: `Fire batch #${index + 1} (${batch} calls)`, builder: async () => { for (let id = 0 ; id !== batch ; ++id, ++lastId) { expectedAnswers.push(lastId); pendingQueries.push(queued(lastId).then(v => (seenAnswers.push(v)))); } }, } })); await s.waitFor(task); await s.waitFor(Promise.all(pendingQueries)); // Assert expect(seenAnswers).toEqual(expectedAnswers); })) })
Tests
Your test should help us to detect a bug in our current implementation of queue
.
Hint #1
One potential solution is to wrap the scheduled API in an additional layer that sets a flag to true
when the API is called, directly invokes the scheduled API, and then resets the flag to false
when the API resolves. If the flag is already set to true
when the wrapper layer is invoked, it should cause the test to fail.