test: Add flushPromises utility function

This commit is contained in:
Joachim Van Herwegen 2022-03-31 12:07:02 +02:00
parent 76548011f2
commit 16e9368734
7 changed files with 40 additions and 28 deletions

View File

@ -16,6 +16,8 @@ import { SingleThreadedResourceLocker } from '../../src/util/locking/SingleThrea
import { WrappedExpiringReadWriteLocker } from '../../src/util/locking/WrappedExpiringReadWriteLocker'; import { WrappedExpiringReadWriteLocker } from '../../src/util/locking/WrappedExpiringReadWriteLocker';
import { guardedStreamFrom } from '../../src/util/StreamUtil'; import { guardedStreamFrom } from '../../src/util/StreamUtil';
import { PIM, RDF } from '../../src/util/Vocabularies'; import { PIM, RDF } from '../../src/util/Vocabularies';
import { flushPromises } from '../util/Util';
jest.useFakeTimers('legacy'); jest.useFakeTimers('legacy');
describe('A LockingResourceStore', (): void => { describe('A LockingResourceStore', (): void => {
@ -67,7 +69,7 @@ describe('A LockingResourceStore', (): void => {
// Wait 1000ms and read // Wait 1000ms and read
jest.advanceTimersByTime(1000); jest.advanceTimersByTime(1000);
await new Promise(setImmediate); await flushPromises();
expect(representation.data.destroyed).toBe(true); expect(representation.data.destroyed).toBe(true);
// Verify a timeout error was thrown // Verify a timeout error was thrown
@ -95,7 +97,7 @@ describe('A LockingResourceStore', (): void => {
// Wait 1000ms and watch the stream be destroyed // Wait 1000ms and watch the stream be destroyed
jest.advanceTimersByTime(1000); jest.advanceTimersByTime(1000);
await new Promise(setImmediate); await flushPromises();
expect(representation.data.destroyed).toBe(true); expect(representation.data.destroyed).toBe(true);
// Verify a timeout error was thrown // Verify a timeout error was thrown

View File

@ -1,7 +1,6 @@
import fetch from 'cross-fetch'; import fetch from 'cross-fetch';
import type { App, RedisResourceLocker } from '../../src'; import type { App, RedisResourceLocker } from '../../src';
import { describeIf, flushPromises, getPort } from '../util/Util';
import { describeIf, getPort } from '../util/Util';
import { getDefaultVariables, getTestConfigPath, instantiateFromConfig } from './Config'; import { getDefaultVariables, getTestConfigPath, instantiateFromConfig } from './Config';
/** /**
@ -139,7 +138,7 @@ describeIf('docker', 'A server with a RedisResourceLocker as ResourceLocker', ()
const lock2 = locker.acquire(identifier); const lock2 = locker.acquire(identifier);
const lock3 = locker.acquire(identifier); const lock3 = locker.acquire(identifier);
await new Promise((resolve): any => setImmediate(resolve)); await flushPromises();
const l2 = lock2.then(async(): Promise<void> => { const l2 = lock2.then(async(): Promise<void> => {
res += 'l2'; res += 'l2';

View File

@ -4,6 +4,7 @@ import { AppRunner } from '../../../src/init/AppRunner';
import type { CliExtractor } from '../../../src/init/cli/CliExtractor'; import type { CliExtractor } from '../../../src/init/cli/CliExtractor';
import type { SettingsResolver } from '../../../src/init/variables/SettingsResolver'; import type { SettingsResolver } from '../../../src/init/variables/SettingsResolver';
import { joinFilePath } from '../../../src/util/PathUtil'; import { joinFilePath } from '../../../src/util/PathUtil';
import { flushPromises } from '../../util/Util';
const app: jest.Mocked<App> = { const app: jest.Mocked<App> = {
start: jest.fn(), start: jest.fn(),
@ -315,9 +316,7 @@ describe('AppRunner', (): void => {
new AppRunner().runCliSync({ argv: [ 'node', 'script' ]}); new AppRunner().runCliSync({ argv: [ 'node', 'script' ]});
// Wait until app.start has been called, because we can't await AppRunner.run. // Wait until app.start has been called, because we can't await AppRunner.run.
await new Promise((resolve): void => { await flushPromises();
setImmediate(resolve);
});
expect(ComponentsManager.build).toHaveBeenCalledTimes(1); expect(ComponentsManager.build).toHaveBeenCalledTimes(1);
expect(ComponentsManager.build).toHaveBeenCalledWith({ expect(ComponentsManager.build).toHaveBeenCalledWith({
@ -348,9 +347,7 @@ describe('AppRunner', (): void => {
new AppRunner().runCliSync({ argv: [ 'node', 'script' ]}); new AppRunner().runCliSync({ argv: [ 'node', 'script' ]});
// Wait until app.start has been called, because we can't await AppRunner.runCli. // Wait until app.start has been called, because we can't await AppRunner.runCli.
await new Promise((resolve): void => { await flushPromises();
setImmediate(resolve);
});
expect(write).toHaveBeenCalledTimes(1); expect(write).toHaveBeenCalledTimes(1);
expect(write).toHaveBeenLastCalledWith(expect.stringMatching(/Cause: Fatal/mu)); expect(write).toHaveBeenLastCalledWith(expect.stringMatching(/Cause: Fatal/mu));

View File

@ -7,6 +7,7 @@ import { LockingResourceStore } from '../../../src/storage/LockingResourceStore'
import type { ResourceStore } from '../../../src/storage/ResourceStore'; import type { ResourceStore } from '../../../src/storage/ResourceStore';
import type { ExpiringReadWriteLocker } from '../../../src/util/locking/ExpiringReadWriteLocker'; import type { ExpiringReadWriteLocker } from '../../../src/util/locking/ExpiringReadWriteLocker';
import { guardedStreamFrom } from '../../../src/util/StreamUtil'; import { guardedStreamFrom } from '../../../src/util/StreamUtil';
import { flushPromises } from '../../util/Util';
function emptyFn(): void { function emptyFn(): void {
// Empty // Empty
@ -170,7 +171,7 @@ describe('A LockingResourceStore', (): void => {
registerEventOrder(representation.data, 'end'); registerEventOrder(representation.data, 'end');
// Provide opportunity for async events // Provide opportunity for async events
await new Promise(setImmediate); await flushPromises();
// Verify the lock was acquired and released at the right time // Verify the lock was acquired and released at the right time
expect(locker.withReadLock).toHaveBeenCalledTimes(1); expect(locker.withReadLock).toHaveBeenCalledTimes(1);
@ -187,7 +188,7 @@ describe('A LockingResourceStore', (): void => {
registerEventOrder(representation.data, 'end'); registerEventOrder(representation.data, 'end');
// Provide opportunity for async events // Provide opportunity for async events
await new Promise(setImmediate); await flushPromises();
// Verify the lock was acquired and released at the right time // Verify the lock was acquired and released at the right time
expect(locker.withReadLock).toHaveBeenCalledTimes(1); expect(locker.withReadLock).toHaveBeenCalledTimes(1);
@ -205,7 +206,7 @@ describe('A LockingResourceStore', (): void => {
registerEventOrder(representation.data, 'close'); registerEventOrder(representation.data, 'close');
// Provide opportunity for async events // Provide opportunity for async events
await new Promise(setImmediate); await flushPromises();
// Verify the lock was acquired and released at the right time // Verify the lock was acquired and released at the right time
expect(locker.withReadLock).toHaveBeenCalledTimes(1); expect(locker.withReadLock).toHaveBeenCalledTimes(1);
@ -222,7 +223,7 @@ describe('A LockingResourceStore', (): void => {
registerEventOrder(representation.data, 'close'); registerEventOrder(representation.data, 'close');
// Provide opportunity for async events // Provide opportunity for async events
await new Promise(setImmediate); await flushPromises();
// Verify the lock was acquired and released at the right time // Verify the lock was acquired and released at the right time
expect(locker.withReadLock).toHaveBeenCalledTimes(1); expect(locker.withReadLock).toHaveBeenCalledTimes(1);
@ -241,7 +242,7 @@ describe('A LockingResourceStore', (): void => {
}); });
// Provide opportunity for async events // Provide opportunity for async events
await new Promise(setImmediate); await flushPromises();
// Verify the lock was acquired and released at the right time // Verify the lock was acquired and released at the right time
expect(locker.withReadLock).toHaveBeenCalledTimes(1); expect(locker.withReadLock).toHaveBeenCalledTimes(1);
@ -258,7 +259,7 @@ describe('A LockingResourceStore', (): void => {
timeoutTrigger.emit('timeout'); timeoutTrigger.emit('timeout');
// Provide opportunity for async events // Provide opportunity for async events
await new Promise(setImmediate); await flushPromises();
// Verify the lock was acquired and released at the right time // Verify the lock was acquired and released at the right time
expect(locker.withReadLock).toHaveBeenCalledTimes(1); expect(locker.withReadLock).toHaveBeenCalledTimes(1);

View File

@ -8,6 +8,7 @@ import {
guardedStreamFrom, pipeSafely, transformSafely, guardedStreamFrom, pipeSafely, transformSafely,
readableToString, readableToQuads, readJsonStream, getSingleItem, readableToString, readableToQuads, readJsonStream, getSingleItem,
} from '../../../src/util/StreamUtil'; } from '../../../src/util/StreamUtil';
import { flushPromises } from '../../util/Util';
jest.mock('../../../src/logging/LogUtil', (): any => { jest.mock('../../../src/logging/LogUtil', (): any => {
const logger: Logger = { warn: jest.fn(), log: jest.fn() } as any; const logger: Logger = { warn: jest.fn(), log: jest.fn() } as any;
@ -132,7 +133,7 @@ describe('StreamUtil', (): void => {
piped.destroy(new Error('this causes an unpipe!')); piped.destroy(new Error('this causes an unpipe!'));
// Allow events to propagate // Allow events to propagate
await new Promise(setImmediate); await flushPromises();
expect(input.destroyed).toBe(true); expect(input.destroyed).toBe(true);
}); });
@ -149,7 +150,7 @@ describe('StreamUtil', (): void => {
piped.destroy(new Error('error!')); piped.destroy(new Error('error!'));
// Allow events to propagate // Allow events to propagate
await new Promise(setImmediate); await flushPromises();
expect(input.destroyed).toBe(false); expect(input.destroyed).toBe(false);
}); });

View File

@ -5,6 +5,7 @@ import { ForbiddenHttpError } from '../../../../src/util/errors/ForbiddenHttpErr
import { InternalServerError } from '../../../../src/util/errors/InternalServerError'; import { InternalServerError } from '../../../../src/util/errors/InternalServerError';
import { GreedyReadWriteLocker } from '../../../../src/util/locking/GreedyReadWriteLocker'; import { GreedyReadWriteLocker } from '../../../../src/util/locking/GreedyReadWriteLocker';
import type { ResourceLocker } from '../../../../src/util/locking/ResourceLocker'; import type { ResourceLocker } from '../../../../src/util/locking/ResourceLocker';
import { flushPromises } from '../../../util/Util';
// A simple ResourceLocker that keeps a queue of lock requests // A simple ResourceLocker that keeps a queue of lock requests
class MemoryLocker implements ResourceLocker { class MemoryLocker implements ResourceLocker {
@ -86,7 +87,7 @@ describe('A GreedyReadWriteLocker', (): void => {
})); }));
// Allow time to attach listeners // Allow time to attach listeners
await new Promise(setImmediate); await flushPromises();
emitter.emit('release2'); emitter.emit('release2');
await expect(promises[2]).resolves.toBe(2); await expect(promises[2]).resolves.toBe(2);
@ -112,12 +113,12 @@ describe('A GreedyReadWriteLocker', (): void => {
})); }));
// Allow time to attach listeners // Allow time to attach listeners
await new Promise(setImmediate); await flushPromises();
emitter.emit('release2'); emitter.emit('release2');
// Allow time to finish write 2 // Allow time to finish write 2
await new Promise(setImmediate); await flushPromises();
emitter.emit('release0'); emitter.emit('release0');
emitter.emit('release1'); emitter.emit('release1');
@ -140,7 +141,7 @@ describe('A GreedyReadWriteLocker', (): void => {
})); }));
// Allow time to attach listeners // Allow time to attach listeners
await new Promise(setImmediate); await flushPromises();
emitter.emit('release1'); emitter.emit('release1');
await expect(promises[1]).resolves.toBe(1); await expect(promises[1]).resolves.toBe(1);
@ -178,7 +179,7 @@ describe('A GreedyReadWriteLocker', (): void => {
}); });
// Allow time to attach listeners // Allow time to attach listeners
await new Promise(setImmediate); await flushPromises();
const promAll = Promise.all([ delayedLockWrite, lockRead ]); const promAll = Promise.all([ delayedLockWrite, lockRead ]);
@ -213,7 +214,7 @@ describe('A GreedyReadWriteLocker', (): void => {
}); });
// Allow time to attach listeners // Allow time to attach listeners
await new Promise(setImmediate); await flushPromises();
const promAll = Promise.all([ delayedLockWrite, lockRead ]); const promAll = Promise.all([ delayedLockWrite, lockRead ]);
@ -260,14 +261,14 @@ describe('A GreedyReadWriteLocker', (): void => {
}); });
// Allow time to attach listeners // Allow time to attach listeners
await new Promise(setImmediate); await flushPromises();
const promAll = Promise.all([ delayedLockWrite, lockRead, delayedLockRead2 ]); const promAll = Promise.all([ delayedLockWrite, lockRead, delayedLockRead2 ]);
emitter.emit('releaseRead1'); emitter.emit('releaseRead1');
// Allow time to finish read 1 // Allow time to finish read 1
await new Promise(setImmediate); await flushPromises();
emitter.emit('releaseRead2'); emitter.emit('releaseRead2');
await promAll; await promAll;
@ -302,7 +303,7 @@ describe('A GreedyReadWriteLocker', (): void => {
}); });
// Allow time to attach listeners // Allow time to attach listeners
await new Promise(setImmediate); await flushPromises();
const promAll = Promise.all([ delayedLockRead, lockWrite ]); const promAll = Promise.all([ delayedLockRead, lockWrite ]);

View File

@ -44,6 +44,17 @@ export function describeIf(envFlag: string, name: string, fn: () => void): void
return enabled ? describe(name, fn) : describe.skip(name, fn); return enabled ? describe(name, fn) : describe.skip(name, fn);
} }
/**
* This is needed when you want to wait for all promises to resolve.
* Also works when using jest.useFakeTimers().
* For more details see the links below
* - https://github.com/facebook/jest/issues/2157
* - https://stackoverflow.com/questions/52177631/jest-timer-and-promise-dont-work-well-settimeout-and-async-function
*/
export async function flushPromises(): Promise<void> {
return new Promise(jest.requireActual('timers').setImmediate);
}
/** /**
* Mocks (some) functions of the fs system library. * Mocks (some) functions of the fs system library.
* It is important that you call `jest.mock('fs');` in your test file before calling this!!! * It is important that you call `jest.mock('fs');` in your test file before calling this!!!