retry-assert
Version:
Retry a function until or while it passes an assertion
344 lines (297 loc) • 8.57 kB
JavaScript
const { expect } = require('expect')
const retry = require('../retry-assert')
function fail(message) {
throw new Error(`Fail: ${message}`);
}
function spy (fn) {
let invocations = []
function getCount () {
return invocations.length
}
function counter () {
invocations.push({
timestamp: Date.now()
})
return fn()
}
counter.getCount = getCount
return counter
}
function incrementer(n=0, fn=x=>x) {
return () => fn(n++)
}
const originalDefaultRetryDelay = retry.defaultRetryDelay
const originalDefaultTimeout = retry.defaultTimeout
beforeEach(() => {
retry.defaultRetryDelay = originalDefaultRetryDelay
retry.defaultTimeout = originalDefaultTimeout
})
describe('until', () => {
beforeEach(() => {
retry.defaultRetryDelay = 10
retry.defaultTimeout = 50
})
test('fn invoked until assertion passes', async () => {
const fn = spy(incrementer())
const result = await retry(fn)
.withRetryDelay(10)
.until(result => expect(result).toEqual(2))
expect(result).toEqual(2)
expect(fn.getCount()).toEqual(3)
})
test('fn provided via longform', async () => {
const fn = spy(incrementer())
const result = await retry()
.fn(fn)
.withRetryDelay(10)
.until(result => expect(result).toEqual(0))
expect(result).toEqual(0)
expect(fn.getCount()).toEqual(1)
})
test('fn invoked until assertion not throwing exception', async () => {
const fn = spy(incrementer())
const result = await retry(fn)
.withRetryDelay(10)
.until(result => {
if (result < 2) {
throw new Error('too small')
}
})
expect(result).toEqual(2)
expect(fn.getCount()).toEqual(3)
})
test('fn invoked until not throwing exception', async () => {
const fn = spy(incrementer(0, n => {
if (n < 2) {
throw new Error('too small')
}
return n
}))
const result = await retry(fn)
.withRetryDelay(10)
.until(result => expect(result).toEqual(2))
expect(result).toEqual(2)
expect(fn.getCount()).toEqual(3)
})
test('fn invoked once if assertion passes', async () => {
const fn = spy(() => 1)
const result = await retry(fn)
.until(result => expect(result).toEqual(1))
expect(result).toEqual(1)
expect(fn.getCount()).toEqual(1)
})
test('fn may return promise', async () => {
const fn = spy(() => Promise.resolve(1))
const result = await retry(fn)
.until(result => expect(result).toEqual(1))
expect(result).toEqual(1)
expect(fn.getCount()).toEqual(1)
})
test('exception thrown when assertion never passes', async () => {
const fn = () => 0
try {
await retry(fn)
.withRetryDelay(1)
.withTimeout(1)
.until(result => expect(result).toEqual(1))
fail('expected promise rejection')
} catch (e) {
expect(e.message).toContain('Expected:')
}
})
test('exception thrown function returns rejected promise', async () => {
const fn = () => Promise.reject('fn failed')
try {
await retry(fn)
.withRetryDelay(1)
.withTimeout(1)
.until(result => expect(1).toEqual(1))
fail('expected promise rejection')
} catch (e) {
console.log(e)
expect(e).toContain('fn failed')
}
})
})
describe('untilTruthy', () => {
beforeEach(() => {
retry.defaultRetryDelay = 10
retry.defaultTimeout = 50
})
test('fn invoked until predicate matches', async () => {
const fn = spy(incrementer())
const result = await retry(fn)
.withRetryDelay(10)
.untilTruthy(n => n === 3)
expect(result).toEqual(3)
expect(fn.getCount()).toEqual(4)
})
test('fn invoked until truthy', async () => {
const fn = spy(incrementer())
const result = await retry(fn)
.withRetryDelay(10)
.untilTruthy()
expect(result).toEqual(1)
expect(fn.getCount()).toEqual(2)
})
test('fn invoked once if truthy', async () => {
const fn = spy(() => 'truthy')
const result = await retry(fn)
.untilTruthy()
expect(result).toEqual('truthy')
expect(fn.getCount()).toEqual(1)
})
test('exception thrown when never truthy', async () => {
const fn = () => 0
try {
await retry(fn)
.withRetryDelay(1)
.withTimeout(1)
.untilTruthy()
fail('expected promise rejection')
} catch (e) {
expect(e.message).toEqual('predicate did not match')
}
})
test('exception thrown when predicate throws', async () => {
const fn = spy(() => 0)
try {
await retry(fn)
.withRetryDelay(1)
.withTimeout(1)
.untilTruthy(x => { throw new Error('predicate failed') })
fail('expected promise rejection')
} catch (e) {
expect(e.message).toEqual('predicate failed')
expect(fn.getCount()).toEqual(2)
}
})
})
describe('ensure', () => {
beforeEach(() => {
retry.defaultRetryDelay = 10
retry.defaultTimeout = 50
})
test('fn invoked max times if assertion passes', async () => {
const fn = spy(() => 1)
const result = await retry(fn)
.withRetryDelay(10)
.withTimeout(50)
.ensure(result => expect(result).toEqual(1))
expect(result).toEqual(1)
expect(fn.getCount()).toEqual(6)
})
test('fn may return promise', async () => {
const fn = spy(() => Promise.resolve(1))
const result = await retry(fn)
.withRetryDelay(10)
.withTimeout(20)
.ensure(result => expect(result).toEqual(1))
expect(result).toEqual(1)
expect(fn.getCount()).toEqual(3)
})
test('exception thrown if assertion fails', async () => {
const fn = spy(() => '')
try {
await retry(fn)
.ensure(result => expect(result).toEqual('x'))
fail('expected promise rejection')
} catch(e) {
expect(e.message).toContain('Expected:')
expect(fn.getCount()).toEqual(1)
}
})
test('exception thrown if fn returns rejected promise', async () => {
const fn = spy(() => Promise.reject('fn failed'))
try {
await retry(fn)
.ensure(result => expect(result).toEqual('x'))
fail('expected promise rejection')
} catch(e) {
expect(e).toContain('fn failed')
expect(fn.getCount()).toEqual(1)
}
})
test('fn invoked while assertion passes', async () => {
const fn = spy(incrementer())
try {
await retry(fn)
.withRetryDelay(10)
.withTimeout(50)
.ensure(result => expect(result).toBeLessThan(2))
fail('expected promise rejection')
} catch (e) {
expect(fn.getCount()).toEqual(3)
}
})
})
describe('ensureTruthy', () => {
beforeEach(() => {
retry.defaultRetryDelay = 10
retry.defaultTimeout = 50
})
test('fn invoked max times if truthy', async () => {
const fn = spy(() => 'truthy')
const result = await retry(fn)
.withRetryDelay(10)
.withTimeout(50)
.ensureTruthy()
expect(result).toEqual('truthy')
expect(fn.getCount()).toEqual(6)
})
test('fn invoked once if falsey', async () => {
const fn = spy(() => '')
try {
await retry(fn)
.ensureTruthy()
fail('expected promise rejection')
} catch(e) {
expect(e.message).toEqual('predicate did not match')
expect(fn.getCount()).toEqual(1)
}
})
test('fn invoked once if predicate throws', async () => {
const fn = spy(() => 0)
try {
await retry(fn)
.withRetryDelay(1)
.withTimeout(1)
.ensureTruthy(x => { throw new Error('predicate failed') })
fail('expected promise rejection')
} catch (e) {
expect(e.message).toEqual('predicate failed')
expect(fn.getCount()).toEqual(1)
}
})
test('fn invoked while predicate truthy', async () => {
const fn = spy(incrementer())
try {
await retry(fn)
.withRetryDelay(10)
.withTimeout(50)
.ensureTruthy(result => result < 2)
fail('expected promise rejection')
} catch (e) {
expect(fn.getCount()).toEqual(3)
}
})
})
describe('validation', () => {
test('retry function required', async () => {
try {
await retry()
.untilTruthy()
fail('expected promise rejection')
} catch (e) {
expect(e.message).toEqual('retry function undefined or not a function')
}
})
})
describe('defaults', () => {
test('timeout', async () => {
expect(retry()._timeout).toEqual(1000)
})
test('retryDelay', async () => {
expect(retry()._delay).toEqual(200)
})
})