Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion lib/mocha/asyncWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import recorder from '../recorder.js'
import assertThrown from '../assert/throws.js'
import { ucfirst, isAsyncFunction } from '../utils.js'
import { getInjectedArguments } from './inject.js'
import { fireHook } from './hooks.js'
import { fireHook, BeforeSuiteHook, AfterSuiteHook } from './hooks.js'

const injectHook = function (inject, suite) {
try {
Expand Down Expand Up @@ -232,6 +232,10 @@ export function suiteSetup(suite) {

// Set up error handler for suite setup
recorder.errHandler(err => {
// A helper's `_beforeSuite()` runs through this hook, not through the
// `injected()` wrapper, so nothing here used to emit `hook.failed` and
// reporters listening for it never saw the failure. (#5660)
event.emit(event.hook.failed, new BeforeSuiteHook(suite, err))
doneFn(err)
})

Expand All @@ -254,6 +258,8 @@ export function suiteTeardown(suite) {

// Set up error handler for suite teardown
recorder.errHandler(err => {
// Same for a helper's `_afterSuite()`. (#5660)
event.emit(event.hook.failed, new AfterSuiteHook(suite, err))
doneFn(err)
})

Expand Down
44 changes: 44 additions & 0 deletions test/unit/mocha/asyncWrapper_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -269,4 +269,48 @@ describe('AsyncWrapper', () => {
expect(arg, 'done called with no error').to.be.undefined
})
})
describe('helper lifecycle hook failures (#5660)', () => {
beforeEach(() => recorder.start())

// A helper's _beforeSuite()/_afterSuite() is queued on the recorder from an
// event.suite.before/after listener (lib/listener/helpers.js), not through
// the injected() wrapper, so it lands in suiteSetup/suiteTeardown's
// errHandler rather than in the path that fires hook.failed.
function queueFailingHelperHook(evt, message) {
event.dispatcher.on(evt, () => {
recorder.add(`hook MyHelper.${message}()`, () => {
throw new Error(message)
})
recorder.catch()
})
}

it('suiteSetup(): a failing helper _beforeSuite emits hook.failed', async () => {
const failed = sinon.spy()
event.dispatcher.on(event.hook.failed, failed)
queueFailingHelperHook(event.suite.before, '_beforeSuite')

const suite = { title: 'Login', ctx: { test: { title: 'codeceptjs.beforeSuite' } } }
const { arg } = await runHook(suiteSetup(suite), 2000)

expect(arg).to.be.instanceof(Error)
expect(arg.message).to.equal('_beforeSuite')
expect(failed.called, 'hook.failed was emitted').to.be.true
expect(failed.firstCall.args[0].hookName).to.equal('BeforeSuite')
expect(failed.firstCall.args[0].err).to.equal(arg)
})

it('suiteTeardown(): a failing helper _afterSuite emits hook.failed', async () => {
const failed = sinon.spy()
event.dispatcher.on(event.hook.failed, failed)
queueFailingHelperHook(event.suite.after, '_afterSuite')

const suite = { title: 'Login', ctx: { test: { title: 'codeceptjs.afterSuite' } } }
const { arg } = await runHook(suiteTeardown(suite), 2000)

expect(arg).to.be.instanceof(Error)
expect(failed.called, 'hook.failed was emitted').to.be.true
expect(failed.firstCall.args[0].hookName).to.equal('AfterSuite')
})
})
})
Loading