unify usage of sleepAsync and add tests

The tests mock JS setTimeout API. However promise.resolve() is not working without flushing the promise queue (which could be done just by awaiting Promise.resolve()), similar issue has been discussed in facebook/jest#2157.
This commit is contained in:
undergroundwires
2021-05-04 19:10:10 +02:00
parent 49600c5f37
commit 36f0805590
4 changed files with 88 additions and 4 deletions

View File

@@ -0,0 +1,53 @@
import 'mocha';
import { expect } from 'chai';
import { AsyncLazy } from '@/infrastructure/Threading/AsyncLazy';
import { sleepAsync } from '@/infrastructure/Threading/AsyncSleep';
describe('AsyncLazy', () => {
it('returns value from lambda', async () => {
// arrange
const expected = 'test';
const lambda = () => Promise.resolve(expected);
const sut = new AsyncLazy(lambda);
// act
const actual = await sut.getValueAsync();
// assert
expect(actual).to.equal(expected);
});
describe('when running multiple times', () => {
// arrange
let totalExecuted: number = 0;
beforeEach(() => totalExecuted = 0);
it('when running sync', async () => {
// act
const sut = new AsyncLazy(() => {
totalExecuted++;
return Promise.resolve(totalExecuted);
});
const results = new Array<number>();
for (let i = 0; i < 5; i++) {
results.push(await sut.getValueAsync());
}
// assert
expect(totalExecuted).to.equal(1);
expect(results).to.deep.equal([1, 1, 1, 1, 1]);
});
it('when running long-running task in parallel', async () => {
// act
const sut = new AsyncLazy(async () => {
await sleepAsync(100);
totalExecuted++;
return Promise.resolve(totalExecuted);
});
const results = await Promise.all([
sut.getValueAsync(),
sut.getValueAsync(),
sut.getValueAsync(),
sut.getValueAsync(),
sut.getValueAsync()]);
// assert
expect(totalExecuted).to.equal(1);
expect(results).to.deep.equal([1, 1, 1, 1, 1]);
});
});
});

View File

@@ -0,0 +1,79 @@
import 'mocha';
import { expect } from 'chai';
import { sleepAsync, SchedulerType } from '@/infrastructure/Threading/AsyncSleep';
describe('AsyncSleep', () => {
it('fulfills after delay', async () => {
// arrange
const delayInMs = 10;
const scheduler = new SchedulerMock();
// act
const sleep = sleepAsync(delayInMs, scheduler.mock);
const promiseState = watchPromiseState(sleep);
scheduler.tickNext(delayInMs);
await flushPromiseResolutionQueue();
// assert
const actual = promiseState.isFulfilled();
expect(actual).to.equal(true);
});
it('pending before delay', async () => {
// arrange
const delayInMs = 10;
const scheduler = new SchedulerMock();
// act
const sleep = sleepAsync(delayInMs, scheduler.mock);
const promiseState = watchPromiseState(sleep);
scheduler.tickNext(delayInMs / 5);
await flushPromiseResolutionQueue();
// assert
const actual = promiseState.isPending();
expect(actual).to.equal(true);
});
});
function flushPromiseResolutionQueue() {
return Promise.resolve();
}
class SchedulerMock {
public readonly mock: SchedulerType;
private currentTime = 0;
private scheduledActions = new Array<{time: number, action: (...args: any[]) => void}>();
constructor() {
this.mock = (callback: (...args: any[]) => void, ms: number) => {
this.scheduledActions.push({ time: this.currentTime + ms, action: callback });
};
}
public tickNext(ms: number) {
const newTime = this.currentTime + ms;
let newActions = this.scheduledActions;
for (const action of this.scheduledActions) {
if (newTime >= action.time) {
newActions = newActions.filter((a) => a !== action);
action.action();
}
}
this.scheduledActions = newActions;
}
}
function watchPromiseState<T>(promise: Promise<T>) {
let isPending = true;
let isRejected = false;
let isFulfilled = false;
promise.then(
() => {
isFulfilled = true;
isPending = false;
},
() => {
isRejected = true;
isPending = false;
},
);
return {
isFulfilled: () => isFulfilled,
isPending: () => isPending,
isRejected: () => isRejected,
};
}