Skip to content

Commit

Permalink
fix: not.toHaveLog no longer returns a false pass when waiting for Cl…
Browse files Browse the repository at this point in the history
…oudWatch to populate
  • Loading branch information
Dane Mauland authored and erezrokah committed Oct 21, 2022
1 parent 66d0af7 commit f91ed36
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 3 deletions.
4 changes: 2 additions & 2 deletions src/jest/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { toHaveRecord } from './kinesis';
import { toHaveObject } from './s3';
import { toHaveMessage } from './sqs';
import { toBeAtState, toHaveState } from './stepFunctions';
import { wrapWithRetries } from './utils';
import { wrapWithRetries, wrapWithRetryUntilPass } from './utils';

declare global {
namespace jest {
Expand All @@ -32,7 +32,7 @@ declare global {
expect.extend({
toBeAtState: wrapWithRetries(toBeAtState) as typeof toBeAtState,
toHaveItem: wrapWithRetries(toHaveItem) as typeof toHaveItem,
toHaveLog: wrapWithRetries(toHaveLog) as typeof toHaveLog,
toHaveLog: wrapWithRetryUntilPass(toHaveLog) as typeof toHaveLog,
toHaveMessage: wrapWithRetries(toHaveMessage) as typeof toHaveMessage,
toHaveObject: wrapWithRetries(toHaveObject) as typeof toHaveObject,
toHaveRecord, // has built in timeout mechanism due to how kinesis consumer works
Expand Down
93 changes: 92 additions & 1 deletion src/jest/utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-var-requires */
import { ICommonProps } from '../common';
import { wrapWithRetries } from './utils';
import { wrapWithRetries, wrapWithRetryUntilPass } from './utils';

jest.mock('../common');

Expand Down Expand Up @@ -111,4 +111,95 @@ describe('utils', () => {
expect(sleep).toHaveBeenCalledWith(500); // default pollEvery
});
});

describe('wrapWithRetryUntilPass', () => {
beforeEach(() => {
jest.clearAllMocks();
});

test('should retry once on pass === true', async () => {
const toWrap = jest.fn();
const expectedResult = { pass: true, message: () => '' };
toWrap.mockReturnValue(Promise.resolve(expectedResult));

const matcherUtils = {} as jest.MatcherUtils;

const props = { region: 'region' } as ICommonProps;
const key = 'key';

const wrapped = wrapWithRetryUntilPass(toWrap);
const result = await wrapped.bind(matcherUtils)(props, key);

expect(toWrap).toHaveBeenCalledTimes(1);
expect(toWrap).toHaveBeenCalledWith(props, key);
expect(result).toBe(expectedResult);
});

test('should exhaust timeout on pass === false', async () => {
const { sleep } = require('../common');

const mockedNow = jest.fn();
Date.now = mockedNow;
mockedNow.mockReturnValueOnce(0);
mockedNow.mockReturnValueOnce(250);
mockedNow.mockReturnValueOnce(500);
mockedNow.mockReturnValueOnce(750);
mockedNow.mockReturnValueOnce(1000);
mockedNow.mockReturnValueOnce(1250);

const toWrap = jest.fn();
const expectedResult = { pass: false, message: () => '' };
toWrap.mockReturnValue(Promise.resolve(expectedResult));

const matcherUtils = {} as jest.MatcherUtils;

const props = { timeout: 1001, pollEvery: 250 } as ICommonProps;
const key = 'key';

const wrapped = wrapWithRetryUntilPass(toWrap);
const result = await wrapped.bind(matcherUtils)(props, key);

expect(toWrap).toHaveBeenCalledTimes(5);
expect(toWrap).toHaveBeenCalledWith(props, key);
expect(result).toBe(expectedResult);
expect(sleep).toHaveBeenCalledTimes(4);
expect(sleep).toHaveBeenCalledWith(props.pollEvery);
});

test('should retry twice, { pass: false, isNot: false } => { pass: true, isNot: false }', async () => {
const { sleep } = require('../common');

const mockedNow = jest.fn();
Date.now = mockedNow;
mockedNow.mockReturnValueOnce(0);
mockedNow.mockReturnValueOnce(250);
mockedNow.mockReturnValueOnce(500);

const toWrap = jest.fn();
// first attempt returns pass === false
toWrap.mockReturnValueOnce(
Promise.resolve({ pass: false, message: () => '' }),
);

// second attempt returns pass === true
const expectedResult = { pass: true, message: () => '' };
toWrap.mockReturnValueOnce(Promise.resolve(expectedResult));

const matcherUtils = {
isNot: false,
} as jest.MatcherUtils;

const props = {} as ICommonProps;
const key = 'key';

const wrapped = wrapWithRetryUntilPass(toWrap);
const result = await wrapped.bind(matcherUtils)(props, key);

expect(toWrap).toHaveBeenCalledTimes(2);
expect(toWrap).toHaveBeenCalledWith(props, key);
expect(result).toBe(expectedResult);
expect(sleep).toHaveBeenCalledTimes(1);
expect(sleep).toHaveBeenCalledWith(500); // default pollEvery
});
});
});
34 changes: 34 additions & 0 deletions src/jest/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,37 @@ export const wrapWithRetries = (
}
return wrapped;
};

export const wrapWithRetryUntilPass = (
matcher: (this: jest.MatcherUtils, ...args: any[]) => Promise<IMatchResult>,
) => {
async function wrapped(
this: jest.MatcherUtils,
props: ICommonProps,
...args: any[]
) {
const { timeout = 2500, pollEvery = 500 } = props;

const start = Date.now();
let result = await (matcher.apply(this, [
props,
...args,
]) as Promise<IMatchResult>);
while (Date.now() - start < timeout) {
// return since result is found
if (result.pass) {
return result;
}

// retry until result is found or timeout
await sleep(pollEvery);

result = await (matcher.apply(this, [
props,
...args,
]) as Promise<IMatchResult>);
}
return result;
}
return wrapped;
};

0 comments on commit f91ed36

Please sign in to comment.