-
-
Notifications
You must be signed in to change notification settings - Fork 751
Expand file tree
/
Copy pathasyncWrapper_test.js
More file actions
272 lines (249 loc) · 8.43 KB
/
Copy pathasyncWrapper_test.js
File metadata and controls
272 lines (249 loc) · 8.43 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
import { expect } from 'chai'
import sinon from 'sinon'
import { test as testWrapper, injected, setup, teardown, suiteSetup, suiteTeardown } from '../../../lib/mocha/asyncWrapper.js'
import recorder from '../../../lib/recorder.js'
import event from '../../../lib/event.js'
import Container from '../../../lib/container.js'
let test
let fn
let before
let after
let beforeSuite
let afterSuite
let failed
let started
// Runs a wrapped test/hook fn and resolves with how many times its done
// callback fired and the argument it received. A done that never fires becomes
// a fast, named rejection instead of a mocha timeout.
function runHook(hookFn, ms = 2000) {
return new Promise((resolve, reject) => {
let count = 0
let arg
const timer = setTimeout(() => reject(new Error('done callback was never called')), ms)
timer.unref?.()
hookFn(err => {
count++
arg = err
const settle = setTimeout(() => {
clearTimeout(timer)
resolve({ count, arg })
}, 50)
settle.unref?.()
})
})
}
describe('AsyncWrapper', () => {
beforeEach(async () => {
test = { timeout: () => {} }
fn = sinon.spy()
test.fn = fn
await Container.create({
helpers: {
FileSystem: {},
},
})
})
beforeEach(() => recorder.reset())
afterEach(() => event.cleanDispatcher())
it('should wrap test function', () => {
testWrapper(test).fn(() => {})
// Return a promise that resolves when the recorder promise resolves
return new Promise(resolve => {
// Use setImmediate to allow the wrapped function to execute
setImmediate(() => {
try {
expect(fn.called).is.ok
resolve()
} catch (err) {
// If the recorder is running, wait for it
if (recorder.isRunning()) {
recorder
.promise()
.then(() => {
expect(fn.called).is.ok
resolve()
})
.catch(resolve)
} else {
throw err
}
}
})
})
})
it('should work with async func', async () => {
let counter = 0
test.fn = async () => {
recorder.add('test', async () => {
counter++
counter++
counter++
counter++
})
}
await setup()
const wrappedTest = testWrapper(test)
// Wait for the wrapped function to complete
await new Promise(resolve => wrappedTest.fn(resolve))
recorder.add('validation', () => expect(counter).to.eq(4))
return recorder.promise()
})
describe('events', () => {
beforeEach(async () => {
event.dispatcher.on(event.test.before, (before = sinon.spy()))
event.dispatcher.on(event.test.after, (after = sinon.spy()))
event.dispatcher.on(event.test.started, (started = sinon.spy()))
event.dispatcher.on(event.suite.before, (beforeSuite = sinon.spy()))
event.dispatcher.on(event.suite.after, (afterSuite = sinon.spy()))
await new Promise(r => suiteSetup()(r))
await new Promise(r => setup()(r))
})
it('should fire events', async () => {
recorder.reset()
const wrappedTest = testWrapper(test)
// Execute the wrapped test function with a mock done callback
return new Promise((resolve, reject) => {
wrappedTest.fn(err => {
;(async () => {
try {
await new Promise(r => teardown()(r))
await new Promise(r => suiteTeardown()(r))
if (err) {
reject(err)
return
}
expect(started.called).is.ok
expect(beforeSuite.called).is.ok
expect(afterSuite.called).is.ok
expect(before.called).is.ok
expect(after.called).is.ok
resolve()
} catch (testErr) {
reject(testErr)
}
})()
})
})
})
it('should fire failed event on error', async () => {
event.dispatcher.on(event.test.failed, (failed = sinon.spy()))
await setup()
test.fn = () => {
throw new Error('ups')
}
testWrapper(test).fn(() => {})
return recorder
.promise()
.then(() => expect(failed.called).is.ok)
.catch(() => null)
})
it('should fire failed event on async error', () => {
test.fn = () => {
recorder.throw(new Error('ups'))
}
testWrapper(test).fn(() => {})
return recorder
.promise()
.then(() => expect(failed.called).is.ok)
.catch(() => null)
})
})
describe('test() lifecycle (characterization)', () => {
beforeEach(() => recorder.start())
it('calls done once with the error and fires test.failed when a queued step throws', async () => {
const onFailed = sinon.spy()
event.dispatcher.on(event.test.failed, onFailed)
test.fn = () => {
recorder.add(() => {
throw new Error('stepfail')
})
}
const { count, arg } = await runHook(testWrapper(test).fn)
expect(count).to.equal(1)
expect(arg).to.be.instanceof(Error)
expect(arg.message).to.equal('stepfail')
expect(onFailed.called, 'test.failed fired').to.be.true
})
it('calls done once with the error when the body throws synchronously', async () => {
const onFailed = sinon.spy()
event.dispatcher.on(event.test.failed, onFailed)
test.fn = () => {
throw new Error('syncthrow')
}
const { count, arg } = await runHook(testWrapper(test).fn)
expect(count).to.equal(1)
expect(arg).to.be.instanceof(Error)
expect(arg.message).to.equal('syncthrow')
expect(onFailed.called, 'test.failed fired').to.be.true
})
it('passes (done with no error) and fires test.passed when test.throws matches', async () => {
const onPassed = sinon.spy()
event.dispatcher.on(event.test.passed, onPassed)
test.throws = /boom/
test.fn = () => {
throw new Error('boom happened')
}
const { count, arg } = await runHook(testWrapper(test).fn)
expect(count).to.equal(1)
expect(arg, 'done called with no error').to.be.undefined
expect(onPassed.called, 'test.passed fired').to.be.true
})
})
describe('injected() hooks (characterization)', () => {
beforeEach(() => recorder.start())
it('a rejecting before-hook calls done with the error and fails the suite tests', async () => {
const onFailed = sinon.spy()
event.dispatcher.on(event.test.failed, onFailed)
const suiteTests = [{ title: 'sample test' }]
const suite = {
opts: {},
ctx: { test: { title: '"before each" hook' }, currentTest: suiteTests[0] },
eachTest: cb => suiteTests.forEach(cb),
}
const fn = async () => {
throw new Error('hookfail')
}
const hook = injected(fn, suite, 'before')
const { arg } = await runHook(hook.bind({ test: {} }))
expect(arg, 'done received the error').to.be.instanceof(Error)
expect(arg.message).to.equal('hookfail')
expect(onFailed.called, 'test.failed emitted for suite tests').to.be.true
expect(suiteTests[0].err, 'the suite test got the hook error attached').to.be.instanceof(Error)
})
})
describe('setup/teardown import hardening (regression)', () => {
beforeEach(() => recorder.start())
it('setup(): a throwing then-body calls done with the error instead of hanging', async () => {
const suite = {
ctx: {
get currentTest() {
throw new Error('setup-boom')
},
},
}
const { count, arg } = await runHook(setup(suite), 1000)
expect(count).to.equal(1)
expect(arg).to.be.instanceof(Error)
expect(arg.message).to.equal('setup-boom')
})
it('teardown(): a throwing then-body calls done with the error instead of hanging', async () => {
const suite = {
ctx: {
get currentTest() {
throw new Error('teardown-boom')
},
},
}
const { count, arg } = await runHook(teardown(suite), 1000)
expect(count).to.equal(1)
expect(arg).to.be.instanceof(Error)
expect(arg.message).to.equal('teardown-boom')
})
it('setup(): happy path calls done with no error', async () => {
const suite = { ctx: { currentTest: { title: 'a sample test' } } }
const { count, arg } = await runHook(setup(suite), 1000)
expect(count).to.equal(1)
expect(arg, 'done called with no error').to.be.undefined
})
})
})